Search code examples
azure-cosmosdbazure-cosmosdb-sqlapi

Cosmos Deserializes On ReadItemAsync Even Through No Results


I'm seeing an issue(?) where my query, which returns no data, is still being deserialized to a class. To illustrate consider the following models:

public class Employee
{
    public Guid Id {get;set;}
    public string Name {get;set;}
    
    public Manager Manager {get;set;}
}

public string Manager
{
    public Guid Id {get;set;}
    public string Name {get;set;}
}


public async Task<Manager?> GetManagerAsync(Guid employeeId, CancellationToken cancellationToken)
{
    var query = $"SELECT value(c.manager) FROM c WHERE c.id = 'cc68c329-38f2-4de7-896c-69ab7cc0d80a'";
    return container.ReadItemAsync<Manager?>(employeeId.ToString(), new PartitionKey(employeeId.ToString()), cancellationToken: cancellationToken)
}

Now consider the following record in within the container which has no manager node at all.

{
    "id": "cc68c329-38f2-4de7-896c-69ab7cc0d80a",
    "name": "mike"
}

I can verify the lack of results by executing the query directly in Cosmos DB. However, when ReadItemAsync executes within GetManagerAsync it takes all the root level properties (id and name) and maps them into the Manager class even though the query is returning no data.

What am I not understanding here and how do this correctly where I can return a null object rather than mapping root properties properties to a child node request?


Solution

  • You created a query and saved it into the variable query, but never actually use it. If you want to query the container using a string query you should call the method container.GetItemQueryIterator.

    Instead what you have used is a point read which returns the literal object without the possibility to perform any transformations on the data. Given your model that should actually be faster than doing a query so that's fine, but you'll have to deserialize it to the actual class of the item:

    var employee = container.ReadItemAsync<Employee>( //<- Used the Employee class
        employeeId.ToString(), 
        new PartitionKey(employeeId.ToString()),
        cancellationToken: cancellationToken);
    return employee.Manager;
    

    And according to your sample data the Manager property should be nullable:

    public class Employee
    {
        public Guid Id {get;set;}
        public string Name {get;set;}
        
        public Manager? Manager {get;set;} //<- added the ?
    }