Generic CRUD Operations For CosmosDB

Introduction

 
Suppose you have an application that needs to connect to a cosmos db instance where you have multiple containers and need to upload different kinds of objects to their appropriate containers. The issue then arises that you will need to create a separate helper for each object most of the time. Instead of doing that we can create a single generic class and reuse most of the code in the class.
 
Note: you will need a cosmos db service already running on the Azure portal, as well as its uri and primary key.

Creating and understanding our CosmosDbHelper

 
Start off by creating a console application. I have used .Net Core to create this application. We will firstly add two class libraries in this project. One library will hold all of our code for the cosmos db helper. The other library will hold all of our models.
 
Generic CRUD Operations For CosmosDB
 
In order to use the cosmos db in our code we will be using the Microsoft.Azure.Cosmos library. This library provides built in functions that we can use for the CRUD. Whereas we will be using these functions and writing our own wrappers around them to suit our needs.
 
The code in the CosmosDbClient.cs file will be the following:
  1. using Microsoft.Azure.Cosmos;  
  2. using System;  
  3. using System.Collections.Generic;  
  4. using System.Linq.Expressions;  
  5. using System.Reflection;  
  6. using System.Text;  
  7. using System.Threading.Tasks;  
  8.   
  9. namespace CosmosDbHelper  
  10. {  
  11.     public class CosmosDbClient<T>  
  12.     {  
  13.         private Container _container;  
  14.   
  15.         public CosmosDbClient(CosmosClient dbClient, string databaseName, string containerName)  
  16.         {  
  17.             this._container = dbClient.GetContainer(databaseName, containerName);  
  18.         }  
  19.   
  20.         public async Task AddItemAsync(T item)  
  21.         {  
  22.             try  
  23.             {  
  24.                 Type myType = item.GetType();  
  25.                 PropertyInfo propertyInfo = myType.GetProperty("Id");  
  26.                 await this._container.CreateItemAsync<T>(item, new PartitionKey(propertyInfo.GetValue(item).ToString()));  
  27.             }  
  28.             catch (Exception e)  
  29.             {  
  30.                 throw e;  
  31.             }  
  32.         }  
  33.   
  34.         public async Task DeleteItemAsync(string id)  
  35.         {  
  36.             await this._container.DeleteItemAsync<T>(id, new PartitionKey(id));  
  37.         }  
  38.   
  39.         public async Task<T> GetItemAsync(string id)  
  40.         {  
  41.             try  
  42.             {  
  43.                 ItemResponse<T> response = await this._container.ReadItemAsync<T>(id, new PartitionKey(id));  
  44.                 return response.Resource;  
  45.             }  
  46.             catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)  
  47.             {  
  48.                 return default(T);  
  49.             }  
  50.   
  51.         }  
  52.   
  53.         public async Task UpdateItemAsync(string id, T item)  
  54.         {  
  55.             await this._container.UpsertItemAsync<T>(item, new PartitionKey(id));  
  56.         }  
  57.     }  
  58. }  
In order to use this single class with every model class that we create we will need to use the template method. Note that when we are creating the item (in the AddItemAsync function) we are using reflection in order to get the id of the item that we are trying to push to the db. This is necessary because there is no other way to get the attribute of an object when it is defined in the form of a generic as there is no certainty as to whether it will have that attribute or not. But in our case the id is necessary so make sure you add an id attribute to every model class that you create.
 
Moving on to our Model class, for now I have added a simple product class
  1. using Newtonsoft.Json;  
  2. using System;  
  3.   
  4. namespace Models  
  5. {  
  6.     public class Product  
  7.     {  
  8.         [JsonProperty(PropertyName = "id")]  
  9.         public string Id { getset; }  
  10.   
  11.         [JsonProperty(PropertyName = "name")]  
  12.         public string Product_Name { getset; }  
  13.   
  14.         [JsonProperty(PropertyName = "description")]  
  15.         public string Product_Description { getset; }  
  16.           
  17.         [JsonProperty(PropertyName = "price")]  
  18.         public string Product_Price { getset; }  
  19.           
  20.         [JsonProperty(PropertyName = "availability")]  
  21.         public string Product_Availability { getset; }  
  22.   
  23.         public override string ToString()  
  24.         {  
  25.             return $"ID: {Id} | Name: {Product_Name} | Price: {Product_Price}";  
  26.         }  
  27.     }  
  28. }  
Now we will be looking at the configuration part.  I have done  that in the Program.cs file in the main driver project for ease but it is recommended that you put these settings in separate configuration files.
  1. using Microsoft.Azure.Cosmos;  
  2. using System;  
  3. using CosmosDbHelper;  
  4. using Models;  
  5. using System.Threading.Tasks;  
  6.   
  7. namespace CosmosDbCruds  
  8. {  
  9.     class Program  
  10.     {  
  11.         static async Task Main(string[] args)  
  12.         {  
  13.             // The Azure Cosmos DB endpoint for running this sample.  
  14.             string EndpointUri = "<your uri>";  
  15.             // The primary key for the Azure Cosmos account.  
  16.             string PrimaryKey = "<your primary key>";  
  17.   
  18.             // The name of the database and container we will create  
  19.             string databaseId = "MyProductCatalog";  
  20.             string containerId = "Products";  
  21.   
  22.             // The Cosmos client instance  
  23.             CosmosClient cosmosClient = new CosmosClient(EndpointUri,PrimaryKey);  
  24.   
  25.             // The database we will create  
  26.             Database database = Task.Run(async () => await cosmosClient.CreateDatabaseIfNotExistsAsync(databaseId)).Result;  
  27.   
  28.             // The container we will create.  
  29.             Container container = Task.Run(async () => await database.CreateContainerIfNotExistsAsync(containerId, "/Id")).Result;  
  30.   
  31.             CosmosDbClient<Product> mClient = new CosmosDbClient<Product>(cosmosClient, databaseId, containerId);  
  32.   
  33.             Product product = new Product  
  34.             {  
  35.                 Id = "1",  
  36.                 Product_Name = "Football",  
  37.                 Product_Description = "A grey foorball with black spots",  
  38.                 Product_Price = "15.00 USD",  
  39.                 Product_Availability = "Unavailable"  
  40.             };  
  41.   
  42.             await mClient.AddItemAsync(product);  
  43.             Console.WriteLine($"Added product: {product}");  
  44.         }  
  45.     }  
  46. }  
Through this process, we have created an object in the cosmos db. You can check by going to the cosmos db service that you created and then going into the data explorer section.
 
Generic CRUD Operations For CosmosDB
 
Basically, you can create any model and it will work with this class as long as you have created the id attribute for that class.
 

Summary

 
We have created in this article a class library that we can use with any model class if are working with Azure Cosmos DB.