Data Operations In Cosmos DB - Part 1

In a previous article we explored ways to automate the creation of database and container in Cosmos DB in the console application.

In this article, we will discuss different methods to perform various data operations in a Cosmos DB container.

Set Up

Lets start by first declaring a record object that defines the properties of the document to be added.

    public record ProductRecord(

    string id,
    string ProductName,
    string ProductCategory,
    string ProductRegion,
    int Quantity,
    string[] PastQuantity,
    Suppliers[] Suppliers
);

    public class Suppliers
    {
        public string SupplierName { get; set; }
        public string SupplierCity { get; set; }

    }

The object has the following properties :

  • id >> Unique GUID

  • ProductName >> Name of the product

  • ProductCategory >> Category of the product

  • ProductRegion >> Region of the product. Remember this also is a partition key

  • Quantity >> Given quantity of the product

  • PastQuantity >> Past quantity of the product. It is a collection

  • Suppliers >> Supplier details like Name & City. This property is an array and can have multiple values

Note : We will use Transactional batch class to perform multiple entry operations in a single batch and insert two documents in the container through the same batch.

  public static async Task<bool> AddNewProducts()
  {
      string id = Guid.NewGuid().ToString();
      string partitionvalue = "region1";
      PartitionKey partitionKey = new(partitionvalue);

      ProductRecord firstitem = new(
          id: id,
          ProductName: "product1",
          Suppliers: new Suppliers[] { new Suppliers { SupplierName = "SupplierForProduct1", SupplierCity = "SupplierCity1" } },
          ProductCategory: "category1",
          ProductRegion: partitionvalue,
          Quantity: 11,
          PastQuantity: new string[] { "5", "2" });

      id = Guid.NewGuid().ToString();
      ProductRecord secondItem = new(
      id: id,
      ProductName: "product2",
      Suppliers: new Suppliers[] { new Suppliers { SupplierName = "SupplierForProduct2", SupplierCity = "SupplierCity2" } },
      ProductCategory: "category2",
      ProductRegion: partitionvalue,
      Quantity: 6,
      PastQuantity: new string[] { "10", "14" });

      TransactionalBatch batch = container.CreateTransactionalBatch(partitionKey)
                                         .CreateItem<ProductRecord>(firstitem)
                                         .CreateItem<ProductRecord>(secondItem);

      using TransactionalBatchResponse response = await batch.ExecuteAsync();

      return response.IsSuccessStatusCode;

  }

Lets break down the code :

Auto create the Guid which sets the values for id property .The partition is set for region called region1. Remember that our partition key is on ProductRegion.

string id = Guid.NewGuid().ToString();
string partitionvalue = "region1";
PartitionKey partitionKey = new(partitionvalue);

Declare a record named firstitem and set the properties and assign it to the ProductRecord object.

ProductRecord firstitem = new(
          id: id,
          ProductName: "product1",
          Suppliers: new Suppliers[] { new Suppliers { SupplierName = "SupplierForProduct1", SupplierCity = "SupplierCity1" } },
          ProductCategory: "category1",
          ProductRegion: partitionvalue,
          Quantity: 11,
          PastQuantity: new string[] { "5", "2" });

Declare a second record named secondItem and set its properties and assign it to ProductRecord object similar to how we did with firstitem.

      id = Guid.NewGuid().ToString();
      ProductRecord secondItem = new(
      id: id,
      ProductName: "product2",
      Suppliers: new Suppliers[] { new Suppliers { SupplierName = "SupplierForProduct2", SupplierCity = "SupplierCity2" } },
      ProductCategory: "category2",
      ProductRegion: partitionvalue,
      Quantity: 6,
      PastQuantity: new string[] { "10", "14" });

Next, create a transaction batch and then assign the two created record items to a single transaction batch called batch for inserting into the container.

 TransactionalBatch batch = container.CreateTransactionalBatch(partitionKey)
                                         .CreateItem<ProductRecord>(firstitem)
                                         .CreateItem<ProductRecord>(secondItem);
using TransactionalBatchResponse response = await batch.ExecuteAsync();

 return response.IsSuccessStatusCode;

Executing the method inserts 2 documents in the container

Will use the id with value : e7be6237-29f6-4077-84f2-95d6d75c4045 to demonstrate further operations. For that purpose a static string variable called id is declared that holds this value .

  static string id = "e7be6237-29f6-4077-84f2-95d6d75c4045";

Method that updates a category name :

  public static async Task<string> UpdateProductDetails()
  {
      string partitionvalue = "region1";


      PartitionKey partitionKey = new(partitionvalue);
      ItemResponse<ProductRecord> response = await container.PatchItemAsync<ProductRecord>(
       id: "e7be6237-29f6-4077-84f2-95d6d75c4045",
       partitionKey: new PartitionKey(partitionvalue),
       patchOperations: new[] {
          PatchOperation.Replace($"/ProductCategory", "Category2")
       });

      return response.StatusCode.ToString();
  }

Values before and after update :

Method that updates supplier name property in the supplier array.

 public static async Task<string> UpdateSuppliertDetails()
 {
     string partitionvalue = "region1";

     PartitionKey partitionKey = new(partitionvalue);
     ItemResponse<ProductRecord> response = await container.PatchItemAsync<ProductRecord>(
      id: id,
      partitionKey: new PartitionKey(partitionvalue),
      patchOperations: new[] {
         PatchOperation.Replace($"/Suppliers/0/SupplierName", "SupplierForProductOne")
      });

     return response.StatusCode.ToString();
 }

Values before and after update :

Method that adds a new supplier details to the supplier array

 public static async Task<string> AddNewSupplierDetails()
 {
     string partitionvalue = "region1";

     PartitionKey partitionKey = new(partitionvalue);

     ItemResponse<ProductRecord> response = await container.PatchItemAsync<ProductRecord>(
      id: id,
      partitionKey: new PartitionKey(partitionvalue),
      patchOperations: new[] {
         PatchOperation.Add("/Suppliers/-",new Suppliers { SupplierName = "SecondSupplierForProductOne", SupplierCity = "SupplierCity2" })
      });

     return response.StatusCode.ToString();
 }

Values before and after the add operation :

Method that adds a new entry to the PastQuantity array

   public static async Task<string> AddNewPastQuantity()
   {
       string partitionvalue = "region1";

       PartitionKey partitionKey = new(partitionvalue);
       ItemResponse<ProductRecord> response = await container.PatchItemAsync<ProductRecord>(
        id: id,
        partitionKey: new PartitionKey(partitionvalue),
        patchOperations: new[] {
            PatchOperation.Add($"/PastQuantity/0", "100")
        });

       return response.StatusCode.ToString();
   }

Note : We define an index position 0 of the array to insert the value in the array.

 PatchOperation.Add($"/PastQuantity/0", "100")

Values before and after the add operation :

Update an existing value of PastQuantity

public static async Task<string> UpdatePastQuantity()
{
    string partitionvalue = "region1";

    PartitionKey partitionKey = new(partitionvalue);
    ItemResponse<ProductRecord> response = await container.PatchItemAsync<ProductRecord>(
     id: id,
     partitionKey: new PartitionKey(partitionvalue),
     patchOperations: new[] {
         PatchOperation.Replace($"/PastQuantity/0", "110")
     });

    return response.StatusCode.ToString();
}

Note : Similar to the add operation we define an index position 0 of the array to identify the value that needs to be updated.

 PatchOperation.Replace($"/PastQuantity/0", "110")

PastQuantity value of 100 is updated to 110

Delete the newly added supplier

 public static async Task<string> DeleteSupplierDetails()
 {
     string partitionvalue = "region1";

     PartitionKey partitionKey = new(partitionvalue);

     ItemResponse<ProductRecord> response = await container.PatchItemAsync<ProductRecord>(
      id: id,
      partitionKey: new PartitionKey(partitionvalue),
      patchOperations: new[] {
         PatchOperation.Remove("/Suppliers/1")
      });

     return response.StatusCode.ToString();
 }

Note : We define an index position 1 of the array to identify the value that needs to be deleted.

 PatchOperation.Remove("/Suppliers/1")

Pre and post delete

Delete the newly added PastQuantity value of 100

  public static async Task<string> DeletePastQuantity()
  {
      string partitionvalue = "region1";

      PartitionKey partitionKey = new(partitionvalue);
      ItemResponse<ProductRecord> response = await container.PatchItemAsync<ProductRecord>(
       id: id,
       partitionKey: new PartitionKey(partitionvalue),
       patchOperations: new[] {
           PatchOperation.Remove($"/PastQuantity/0")
       });

      return response.StatusCode.ToString();
  }

Note : We define an index position 0 of the array to identify the value that needs to be deleted.

 PatchOperation.Remove($"/PastQuantity/0")

Pre and post delete

Closing Notes :

In conclusion, we saw how to leverage the full potential of this powerful NoSQL database. Understanding the methods and techniques of the fundamental data operations that were discussed above is crucial. I hope this article has provided valuable insights and practical knowledge to help use Cosmos DB in real life project.

In the next article I will continue to delve into the features related to data retrieval in Cosmos DB.

Happy learning !

0
Subscribe to my newsletter

Read articles from Sachin Nandanwar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sachin Nandanwar
Sachin Nandanwar