Home > database >  Is there a way to improve the code? .Net Core 6.0 and Cosmos Database with SQL API
Is there a way to improve the code? .Net Core 6.0 and Cosmos Database with SQL API

Time:09-08

It's my first time using .Net Core 6.0 WebAPI and Cosmos Database with SQL API, so I'm concerned that I've done things correctly.

I have used /address/zipcode as a partition key in the below sample

public class FamilyService : IFamilyService
{
    public FamilyService(CosmosClient dbClient, string databaseName, string containerName)
    {
        this._container = dbClient.GetContainer(databaseName, containerName);
    }

    public Task<Family> GetFamilyDataAsync(string id, string zipcode)
    {
        return Task.FromResult(_container.GetItemLinqQueryable<Family>(allowSynchronousQueryExecution: true)
                        .Where(p => p.Id == id && p.Address.ZipCode == zipcode)
                        .ToList().First());
    }

    public async Task AddFamilyDataAsync(Family family)
    {
        await this._container.CreateItemAsync<Family>(family, new PartitionKey(family.Address.ZipCode));
    }

    public async Task UpdateFamilyDataAsync(Family family)
    {
        await this._container.ReplaceItemAsync<Family>(family, family.Id, new PartitionKey(family.Address.ZipCode));
    }

    public async Task DeleteFamilyDataAsync(string id, string zipcode)
    {
        await this._container.DeleteItemAsync<Family>(id, new PartitionKey(zipcode));
    }

    private async Task<bool> GetFamilyDataFromId(string id, string zipcode)
    {
        string query = $"select * from c where c.id=@familyId";
        QueryDefinition queryDefinition = new QueryDefinition(query).WithParameter("@familyId", id);
        List<Family> familyResults = new List<Family>();
        FeedIterator streamResultSet = _container.GetItemQueryStreamIterator(
         queryDefinition,
         requestOptions: new QueryRequestOptions()
         {
             PartitionKey = new PartitionKey(zipcode),
             MaxItemCount = 10,
             MaxConcurrency = 1
         });

        while (streamResultSet.HasMoreResults)
        {
            using (ResponseMessage responseMessage = await streamResultSet.ReadNextAsync())
            {

                if (responseMessage.IsSuccessStatusCode)
                {
                    dynamic streamResponse = FromStream<dynamic>(responseMessage.Content);
                    List<Family> familyResult = streamResponse.Documents.ToObject<List<Family>>();
                    familyResults.AddRange(familyResult);
                }
                else
                {
                    return false;
                }
            }
        }
        if (familyResults != null && familyResults.Count > 0)
        {
            return true;
        }
        return familyResults;
} 

Especially I want to focus more on those methods that retrieves the data from the database.

Update#1 : I have updated the code as suggested by @Mark Brown.

public class FamilyService : IFamilyService
{
    private Container _container;
    private static readonly JsonSerializer Serializer = new JsonSerializer();

    public FamilyService(CosmosClient dbClient, string databaseName, string containerName)
    {
        this._container = dbClient.GetContainer(databaseName, containerName);
    }

    public async Task<Family> GetFamilyDataAsync(string id, string zipCode)
    {
       return await _container.ReadItemAsync<Family>(id, new PartitionKey(zipCode));
    }

    public async Task<List<Family>> GetAllFamilyDataAsync(string zipCode)
    {
        string query = $"SELECT * FROM Family";
        QueryDefinition queryDefinition = new QueryDefinition(query);
        List<Family> familyResults = new List<Family>();

        FeedIterator<Family> feed = _container.GetItemQueryIterator<Family>(
         queryDefinition,
         requestOptions: new QueryRequestOptions()
         {
             PartitionKey = new PartitionKey(zipCode),
             MaxItemCount = 10,
             MaxConcurrency = 1
         });

        while (feed.HasMoreResults)
        {
            FeedResponse<Family> response = await feed.ReadNextAsync();
            foreach (Family item in response)
            {
                familyResults.Add(item);
            }
        }

        return familyResults;
    }

    public async Task<List<Family>> GetAllFamilyDataByLastNameAsync(string lastName, string zipCode)
    {
        string query = $"SELECT * FROM Family where Family.lastName = @lastName";
        QueryDefinition queryDefinition = new QueryDefinition(query).WithParameter("@lastName", lastName);
        List<Family> familyResults = new List<Family>();

        FeedIterator<Family> feed = _container.GetItemQueryIterator<Family>(
         queryDefinition,
         requestOptions: new QueryRequestOptions()
         {
             PartitionKey = new PartitionKey(zipCode),
             MaxItemCount = 10,
             MaxConcurrency = 1
         });

        while (feed.HasMoreResults)
        {
            FeedResponse<Family> response = await feed.ReadNextAsync();
            foreach (Family item in response)
            {
                familyResults.Add(item);
            }
        }

        return familyResults;
    }

    public async Task<List<Family>> GetAllFamilyDataPaginatedAsync(int pageNumber, string zipCode)
    {
        int pageSize = 2;
        int itemsToSkip = (pageSize * pageNumber) - pageSize;
        QueryRequestOptions options = new QueryRequestOptions { MaxItemCount = pageSize };
        string continuationToken = null;
        List<Family> familyResults = new List<Family>();

        IQueryable<Family> query = this._container
                                        .GetItemLinqQueryable<Family>(false, continuationToken, options)
                                        .Where(i => i.Address.ZipCode == zipCode)
                                        .Skip(itemsToSkip);
        using (FeedIterator<Family> iterator = query.ToFeedIterator<Family>())
        {
            FeedResponse<Family> feedResponse = await iterator.ReadNextAsync();

            foreach (Family item in feedResponse)
            {
                familyResults.Add(item);
            }
        }

        return familyResults;
    }

    public async Task AddFamilyDataAsync(Family family)
    {
        await this._container.CreateItemAsync<Family>(family, new PartitionKey(family.Address.ZipCode));
    }

    public async Task UpdateFamilyDataAsync(Family family)
    {
        await this._container.ReplaceItemAsync<Family>(family, family.Id, new PartitionKey(family.Address.ZipCode));
    }

    public async Task DeleteFamilyDataAsync(string id, string zipCode)
    {
        await this._container.DeleteItemAsync<Family>(id, new PartitionKey(zipCode));
    }
}

CodePudding user response:

If you are searching for something with the partition key and id then that can only return a single item as id must be unique within a pk. So GetFamilyDataAsync() can be implemented using ReadItemAsync() rather than a query. PS: Not sure why you would ever do this, allowSynchronousQueryExecution: true

GetFamilyDataFromId() also can be implemented using ReadItemAsync(). Even if this did require a query, it doesn't make sense to implement to return a stream, then deserialize into an object. Just use the variant that can deserialize directly into a POCO.

Can't say if this is completely optimal for everything, but you can go here to find more things as a place to start, https://docs.microsoft.com/en-us/azure/cosmos-db/sql/best-practice-dotnet

The other thing to call out is zip code as a partition key. This might work at small scale, but it could run into issues at larger scale depending on how many families and how many records for each family are stored within a single partition. Max size for a single partition key value is 20 GB. Fine for small workloads but possibly an issue at larger scale.

  • Related