SDK CRUD Operations and Transactions
Master all Cosmos DB data operations: Create, Read, Upsert, Replace, Delete, Patch, optimistic concurrency with ETags, TransactionalBatch, bulk mode, and 429 throttling with automatic retries.
CRUD in Cosmos DB
Think of Cosmos DB as a smart filing drawer. You can: Create (add a new folder), Read (pull out a specific folder by its label), Upsert (add or update β βif it exists, replace; if not, createβ), Replace (swap the entire folder), Delete (remove it), and Patch (change just one page without touching the rest).
The real superpower is TransactionalBatch β do multiple operations as one atomic action: either they all succeed, or none do.
All CRUD operations in C#
Create
var project = new ProjectItem
{
Id = "proj-001",
TenantId = "tenant-abc",
Type = "project",
Name = "Website Redesign",
Status = "active"
};
ItemResponse<ProjectItem> response = await container.CreateItemAsync(
project,
new PartitionKey("tenant-abc"));
Console.WriteLine($"Created. RU charge: {response.RequestCharge}");
// Fails with 409 Conflict if proj-001 already exists in this partition
Read (point read)
ItemResponse<ProjectItem> response = await container.ReadItemAsync<ProjectItem>(
id: "proj-001",
partitionKey: new PartitionKey("tenant-abc"));
ProjectItem project = response.Resource;
Console.WriteLine($"Read '{project.Name}'. RU: {response.RequestCharge}");
// ~1 RU for a 1 KB item β the cheapest operation
Upsert
project.Status = "completed";
ItemResponse<ProjectItem> response = await container.UpsertItemAsync(
project,
new PartitionKey("tenant-abc"));
// If proj-001 exists β replaced. If not β created. No error either way.
Replace
project.Name = "Website Redesign v2";
ItemResponse<ProjectItem> response = await container.ReplaceItemAsync(
project,
id: "proj-001",
partitionKey: new PartitionKey("tenant-abc"));
// Fails with 404 if the item doesn't exist
Delete
ItemResponse<ProjectItem> response = await container.DeleteItemAsync<ProjectItem>(
id: "proj-001",
partitionKey: new PartitionKey("tenant-abc"));
// Fails with 404 if the item doesn't exist
Patch operations
Patch lets you modify specific properties without reading and replacing the entire document:
// Patch: update status and increment a counter
ItemResponse<ProjectItem> response = await container.PatchItemAsync<ProjectItem>(
id: "proj-001",
partitionKey: new PartitionKey("tenant-abc"),
patchOperations: new[]
{
PatchOperation.Set("/status", "in-review"), // Set a value
PatchOperation.Add("/tags/-", "urgent"), // Append to array
PatchOperation.Replace("/name", "Final Design"), // Replace a value
PatchOperation.Remove("/tempFlag"), // Remove a property
PatchOperation.Increment("/viewCount", 1) // Increment number
});
| Patch operation | What it does |
|---|---|
| Set | Sets a property value (creates it if missing) |
| Add | Adds to an array (/- = append) or creates a new property |
| Replace | Replaces an existing property (fails if missing) |
| Remove | Removes a property |
| Increment | Increments a numeric value (atomic β no read-modify-write race) |
| Move | Moves a property from one path to another within the document |
Why Patch matters: Without Patch, updating one field means reading the entire document, changing it in memory, and replacing it β 3 operations and a concurrency risk. Patch does it in 1 operation.
Optimistic concurrency with ETags
Every Cosmos DB item has an _etag property that changes with every update. Use it to prevent lost updates:
// Step 1: Read the item (get current ETag)
ItemResponse<ProjectItem> response = await container.ReadItemAsync<ProjectItem>(
"proj-001", new PartitionKey("tenant-abc"));
string etag = response.ETag;
// Step 2: Replace with ETag check
try
{
ProjectItem project = response.Resource;
project.Status = "approved";
await container.ReplaceItemAsync(
project,
"proj-001",
new PartitionKey("tenant-abc"),
new ItemRequestOptions { IfMatchEtag = etag }); // Only if unchanged
}
catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.PreconditionFailed)
{
// 412 β someone else modified the document. Re-read and retry.
Console.WriteLine("Conflict! Document was modified by another user.");
}
How it works:
- Read the item β get its
_etag - Send a Replace/Upsert with
IfMatchEtag = etag - If the ETag still matches β update succeeds
- If someone changed the document β 412 Precondition Failed β re-read and retry
Exam tip: ETag and 412
The _etag is a system-generated property that acts as a version stamp. When you pass IfMatchEtag in a request, Cosmos DB compares the ETag before writing. A mismatch means someone else modified the document, and you get a 412 Precondition Failed error. This is optimistic concurrency β no locks, just version checks. Know the 412 status code for the exam.
TransactionalBatch
TransactionalBatch provides ACID transactions for multiple operations within a single logical partition:
var project = new ProjectItem
{
Id = "proj-002", TenantId = "tenant-abc", Type = "project",
Name = "Mobile App"
};
var firstTask = new TaskItem
{
Id = "task-101", TenantId = "tenant-abc", Type = "task",
ProjectId = "proj-002", Title = "Set up CI/CD"
};
TransactionalBatchResponse batchResponse = await container
.CreateTransactionalBatch(new PartitionKey("tenant-abc"))
.CreateItem(project)
.CreateItem(firstTask)
.PatchItem("proj-001", new[]
{
PatchOperation.Increment("/childProjectCount", 1)
})
.ExecuteAsync();
if (batchResponse.IsSuccessStatusCode)
{
Console.WriteLine("All operations succeeded atomically.");
}
else
{
Console.WriteLine($"Batch failed: {batchResponse.StatusCode}");
// ALL operations rolled back β nothing was written
}
TransactionalBatch rules:
- All operations must target the same logical partition (same partition key value)
- Maximum 100 operations per batch
- Maximum 2 MB total payload
- All-or-nothing: if any operation fails, the entire batch is rolled back
- Supports: Create, Read, Upsert, Replace, Delete, Patch
Bulk mode
Bulk mode optimises high-volume writes by batching requests behind the scenes:
CosmosClient bulkClient = new CosmosClient(
endpoint, key,
new CosmosClientOptions { AllowBulkExecution = true });
// Create many tasks concurrently β SDK batches them automatically
List<Task> tasks = new();
foreach (var item in itemsToInsert)
{
tasks.Add(container.CreateItemAsync(item, new PartitionKey(item.TenantId)));
}
await Task.WhenAll(tasks);
Bulk mode is NOT transactional β items are written independently. Some may succeed while others fail. Use TransactionalBatch when you need atomicity.
Handling 429 throttling
When you exceed provisioned RU/s, Cosmos DB returns 429 Too Many Requests with a Retry-After header:
| Aspect | Detail |
|---|---|
| Default retries | 9 retries with exponential backoff |
| Retry-After header | Tells you how long to wait (in ms) |
| SDK behaviour | Auto-retries transparently β your code often never sees the 429 |
| Custom config | CosmosClientOptions.MaxRetryAttemptsOnRateLimitedRequests = 15 |
| Custom wait | CosmosClientOptions.MaxRetryWaitTimeOnRateLimitedRequests = TimeSpan.FromSeconds(60) |
var options = new CosmosClientOptions
{
MaxRetryAttemptsOnRateLimitedRequests = 15,
MaxRetryWaitTimeOnRateLimitedRequests = TimeSpan.FromSeconds(60)
};
Raviβs mistake: Ravi turned off retries (MaxRetryAttemptsOnRateLimitedRequests = 0) thinking it would βfail fast.β Instead, his app crashed with hundreds of 429 errors during peak load. The default 9 retries exist for a reason β they smooth out temporary spikes.
Exam tip: batch vs bulk
TransactionalBatch = ACID, same partition, max 100 ops, all-or-nothing. Bulk mode = NOT transactional, optimises throughput by batching network calls, items can be in different partitions, some may succeed while others fail. The exam loves confusing these two. If the question asks about atomicity β batch. If it asks about throughput optimisation β bulk.
π¬ Video walkthrough
π¬ Video coming soon
SDK CRUD & Transactions β DP-420 Module 7
SDK CRUD & Transactions β DP-420 Module 7
~18 minFlashcards
Knowledge check
Priya needs to atomically create a project and its first task in the 'workitems' container (both in the same tenant). Which approach is correct?
Ravi and Sophie both read the same project document (ETag: 'abc123'). Ravi updates it first. What happens when Sophie tries to Replace with IfMatchEtag = 'abc123'?
Priya wants to increment a view counter on a project document without reading it first. Which operation should she use?
Next up: SQL Queries β learn the Cosmos DB SQL query language, including SELECT, WHERE, JOIN (intra-document only), cross-partition queries, and composite indexes.