Search code examples
c#azureasp.net-coreazure-cosmosdbazure-application-insights

Azure Cosmos DB - check if item not exists without throwing error to Application Insights


I built a simple player-tracking API app in ASP.NET Core 3.1 that uses Azure Cosmos DB as its back end.

The API to create a new player entry first checks if an entry with the same ID under a given partition key already exists in Cosmos DB using this:

try
{
    ItemResponse<Player> response = await this._playerContainer.ReadItemAsync<Player>(playerid, new PartitionKey(partitionkey));
    return Conflict();
}
catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
    // There is some more logic happening here so I cannot directly just call CreateItemAsync()... and return the Conflict response that this might return.
    ItemResponse<GameResult> response = await this._gameResultContainer.CreateItemAsync<GameResult>(gameresult, new PartitionKey(gameresult.PlayerId));
    // ...        
    return Accepted();
}

Only if this returns nothing, I go ahead and put the create request in a backend worker queue. Otherwise I return a 409-Conflict to the API caller.

The actual insert happens in an async backend worker. But I want to return to the API caller directly, if his insert will succeed.

All working fine so far. The issue I have is the following: As I am using the Azure Application Insights SDK, any call which does not find an existing item (which should be the normal case here), will automatically create a Error in AppInsights - even though I catch the exception in my code. That's obviously cluttering my logging quite a bit.

Any idea how I can get rid of that or generally how to change the API to get a better behavior for this?


Solution

  • The issue is on the Cosmos DB .NET SDK side. It throws an exception if a document is not found. They can't really change this behavior because clients are relying on it. GitHub Issue

    The suggested workaround is to use lower-level Stream API. This way you'll be able to handle 404 behavior on your side.

    Something like this:

        using (ResponseMessage responseMessage = await container.ReadItemStreamAsync(
            partitionKey: new PartitionKey(partitionkey),
            id: playerid))
        {
            if (responseMessage.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
                ...
                return Accepted();
            }
    
            if (responseMessage.IsSuccessStatusCode)
            {
                return Conflict();
            }
        }
    

    There's sample code in the repo for doing custom deserialization