Wednesday, 14 September 2022

Partially update documents in Azure Cosmos DB

I have been working with Cosmos DB for a while now and until recently, there was one thing which always annoyed me: When updating a JSON document stored in a container, there was no way to only modify a few selected properties of the document. 

The entire JSON document had to be fetched by the client first, then locally replace the properties to be updated, and then send the entire document back to Cosmos DB and replace the previous version of the document. 

This was always a challenge because first, it added more work for developers and second, there could be concurrency issues if multiple clients could be downloading multiple copies of the document and updating the data and sending back their copy.

But fortunately, now it's possible to partially update a document in Cosmos DB and basically do a PATCH operation while only sending the properties to be changed over the wire. 



So in this post, let's have a look at the different ways in which we can partially update documents in Cosmos DB:

Setting up by creating a Cosmos DB document

First, we will create our sample document using code. I should mention I am using v3.30.1 of Azure Cosmos DB .NET core package from nuget: Microsoft.Azure.Cosmos

var endpointUri = "<cosmosdb-endpoint-uri>";
var primaryKey = "<cosmosdb-primary-key>";
var databaseId = "UsersDB";
var containerId = "Users";
var partitionKey = "allusers";
var _cosmosClient = new CosmosClient(endpointUri, primaryKey);
var _databaseResponse = await _cosmosClient.CreateDatabaseIfNotExistsAsync(databaseId);
var _containerResponse = await _databaseResponse.Database.CreateContainerIfNotExistsAsync(containerId, $"/partitionKey");
var user = new User()
{
Id = userIdForDemo,
Name = "Vardhaman",
Department = "Product",
Projects = new Project[] { new Project { Name = "Halo" } },
Skills = new string[] { "M365", "Azure" },
PartitionKey = "allusers"
};
await _containerResponse.Container.CreateItemAsync<User>(user, new PartitionKey(partitionKey));

As you can see this is a simple document representing a User object with a few attached properties:


Now in order to only change a few properties in the object, we need to use the Partial document update feature in Azure Cosmos DB 

Update document properties

Now lets have a look at how we can modify and add properties to the User object:

//Add new property
ItemResponse<User> response = await _containerResponse.Container.PatchItemAsync<User>(
id: userIdForDemo,
partitionKey: new PartitionKey(partitionKey),
patchOperations: new[] {
PatchOperation.Add($"/location", "London")
});
//Update existing property
ItemResponse<User> response = await _containerResponse.Container.PatchItemAsync<User>(
id: userIdForDemo,
partitionKey: new PartitionKey(partitionKey),
patchOperations: new[] {
PatchOperation.Add($"/name", "Vardhaman Deshpande")
});
In the code, we are updating an existing property of the document as well as adding a new property. Also, we are only sending the properties to be modified over the wire. 


We can also send both properties in a single operation by adding the operations to the patchOperations array.

Update elements in array


Just like document properties, we can also update single elements in an array in the document. Elements can be updated, added, added at a specific index and also removed from an array:

//Add new array item at the start
ItemResponse<User> response = await _containerResponse.Container.PatchItemAsync<User>(
id: userIdForDemo,
partitionKey: new PartitionKey(partitionKey),
patchOperations: new[] {
PatchOperation.Add($"/skills/0", "Architecture")
});
//Add new array item at the specified index
ItemResponse<User> response = await _containerResponse.Container.PatchItemAsync<User>(
id: userIdForDemo,
partitionKey: new PartitionKey(partitionKey),
patchOperations: new[] {
PatchOperation.Add($"/skills/2", "Integration")
});
//Append new array item at the end of the array
ItemResponse<User> response = await _containerResponse.Container.PatchItemAsync<User>(
id: userIdForDemo,
partitionKey: new PartitionKey(partitionKey),
patchOperations: new[] {
PatchOperation.Add($"/skills/-", "B2B SaaS")
});
//Update array element at specified index using Set operation
ItemResponse<User> response = await _containerResponse.Container.PatchItemAsync<User>(
id: userIdForDemo,
partitionKey: new PartitionKey(partitionKey),
patchOperations: new[] {
PatchOperation.Set($"/skills/1", "Microsoft 365")
});

Update objects and their properties


The properties of a JSON document can themselves be objects as well. The Azure Cosmos DB patch operations can also be used to update properties of specific objects as well as modifying the objects themselves.

//Update object property from item
ItemResponse<User> response = await _containerResponse.Container.PatchItemAsync<User>(
id: userIdForDemo,
partitionKey: new PartitionKey(partitionKey),
patchOperations: new[] {
PatchOperation.Add($"/projects/0/name", "Halo Infinite")
});
//Add new item to object array
ItemResponse<User> response = await _containerResponse.Container.PatchItemAsync<User>(
id: userIdForDemo,
partitionKey: new PartitionKey(partitionKey),
patchOperations: new[] {
PatchOperation.Add($"/projects/-", new Project(){ Name="Need for Speed" } )
});
//Remove item at specified index from object array
ItemResponse<User> response = await _containerResponse.Container.PatchItemAsync<User>(
id: userIdForDemo,
partitionKey: new PartitionKey(partitionKey),
patchOperations: new[] {
PatchOperation.Remove($"/projects/0" )
});

Hope this helps! For more details, do have a look at the Microsoft docs for Azure Cosmos DB partial updates: https://docs.microsoft.com/en-us/azure/cosmos-db/partial-document-update