Search code examples
c#azure-cosmosdb

Is there any better way to upsert on CosmosDB by not id?


I have a client app where I create models and store locally and also send a mapped DTO to web api.

class TestModel // Locally stored model
    {
       Guid OwnerId;
       Guid LocalId;
       string Name;
    }

class TestDto // Remotely stored model 
    {
       Guid Id;
       Guid OwnerId;
       Guid LocalId;
       string Name;
    }

On Server Side: When I send a mapped DTO to the Web Api I would like to update but because the model is not aware the Id of the database stored DTO I use OwnerId & LocalId to detect the stored DTO to update.

   public async Task<TestDto?> UpdateAsync(TestDto testDto)
   {
       IOrderedQueryable<TestDto> queryable = _container.GetItemLinqQueryable<TestDto>();

       // Construct LINQ query
       var matches = queryable.Where(p => p.LocalId == testDto.LocalId && p.OwnerId == testDto.OwnerId);

       using FeedIterator<TestDto> linqFeed = matches.ToFeedIterator();

       List<TestDto> results = new List<TestDto>();
       while (linqFeed.HasMoreResults)
       {
           var response = await linqFeed.ReadNextAsync();
           results.AddRange((IEnumerable<TestDto>)response);
       }

       var result = results.SingleOrDefault();

       if (result != null && results.Count == 1)
       {
           testDto.Id = result.Id; // need set to update
           await _container.UpsertItemAsync<TestDto>(testDto, new PartitionKey(testDto.OwnerId.ToString()));
       }

       return result;
   }

Is there any better way to filter out? To select the updatable document? Instead of linqFeed and singleOrDefault?


Solution

  • Looks mostly fine. You can improve performance by only selecting the Id property and instead of returning all results only return the first result. Even if you expect only a single result the query will always scan all items instead of returning once it finds any item.

    You will need to retrieve the Id some way so unless you have the Id readily available you'll always have a query run somewhere. There's not much else to improve apart from maybe cleaning up the code a bit for readability.

    using var iterator = container
        .GetItemLinqQueryable<TestDto>()
        .Where(p => p.LocalId == testDto.LocalId && p.OwnerId == testDto.OwnerId)
        .Take(1)
        .Select(p => p.Id)
        .ToFeedIterator();
    
    Guid Id;
    while (iterator.HasMoreResults)
    {
        var feed = await iterator.ReadNextAsync();
        Id = feed.FirstOrDefault();
    }