The SQL API supports optimistic concurrency control (OCC) through HTTP entity tags, or ETags. Every SQL API resource has an ETag, and the ETag is set on the server every time an item is updated. In this exercise, we will view the ETag property of a resource that is requested using the SDK.
If this is your first lab and you have not already completed the setup for the lab content see the instructions for Account Setup before starting this lab.
-
On your local machine, locate the CosmosLabs folder in your Documents folder and open the
Lab10
folder that will be used to contain the content of your .NET Core project. If you are completing this lab through Microsoft Hands-on Labs, the CosmosLabs folder will be located at the path: C:\labs\CosmosLabs -
In the
Lab10
folder, right-click the folder and select the Open with Code menu option.Alternatively, you can run a terminal in your current directory and execute the
code .
command. -
In the Visual Studio Code window that appears, right-click the Explorer pane and select the Open in Terminal menu option.
-
In the terminal pane, enter and execute the following command:
dotnet restore
This command will restore all packages specified as dependencies in the project.
-
In the terminal pane, enter and execute the following command:
dotnet build
This command will build the project.
-
In the Explorer pane verify that you have a DataTypes.cs file in your project folder. This file contains the data classes you will be working with in the following steps.
-
Select the Program.cs link in the Explorer pane to open the file in the editor.
-
For the
_endpointUri
variable, replace the placeholder value with the URI value and for the_primaryKey
variable, replace the placeholder value with the PRIMARY KEY value from your Azure Cosmos DB account. Use these instructions to get these values if you do not already have them:- For example, if your uri is
https://cosmosacct.documents.azure.com:443/
, your new variable assignment will look like this:
private static readonly string _endpointUri = "https://cosmosacct.documents.azure.com:443/";
- For example, if your primary key is
elzirrKCnXlacvh1CRAnQdYVbVLspmYHQyYrhx0PltHi8wn5lHVHFnd1Xm3ad5cn4TUcH4U0MSeHsVykkFPHpQ==
, your new variable assignment will look like this:
private static readonly string _primaryKey = "elzirrKCnXlacvh1CRAnQdYVbVLspmYHQyYrhx0PltHi8wn5lHVHFnd1Xm3ad5cn4TUcH4U0MSeHsVykkFPHpQ==";
- For example, if your uri is
-
Save all of your open editor tabs.
-
In the open terminal pane, enter and execute the following command:
dotnet build
-
Select the Program.cs link in the Explorer pane to open the file in the editor.
-
Locate the
Main
method:public static async Task Main(string[] args) { Database database = _client.GetDatabase(_databaseId); Container container = database.GetContainer(_containerId); }
-
Add the following code to asynchronously read a single item from the container, identified by its partition key and id:
ItemResponse<Food> response = await container.ReadItemAsync<Food>("21083", new PartitionKey("Fast Foods"));
-
Add the following line of code to show the current ETag value of the item:
await Console.Out.WriteLineAsync($"ETag: {response.ETag}");
The ETag header and the current value are included in all response messages.
-
Save all of your open editor tabs.
-
In the open terminal pane, enter and execute the following command:
dotnet run
-
Observe the output of the console application. You should see an ETag for the item.
-
Enter and execute the following command:
dotnet run
-
Observe the output of the console application.
The ETag should remain unchanged since the item has not been changed.
-
Within the
Main
method, locate the following line of code:await Console.Out.WriteLineAsync($"ETag:\t{response.ETag}");
Replace that line of code with the following code:
await Console.Out.WriteLineAsync($"Existing ETag:\t{response.ETag}");
-
Next, add a new line of code to create an ItemRequestOptions instance that will use the ETag from the item and specify an If-Match header:
ItemRequestOptions requestOptions = new ItemRequestOptions { IfMatchEtag = response.ETag };
-
Add a new line of code to update a property of the retrieved item:
response.Resource.tags.Add(new Tag { name = "Demo" });
This line of code will modify a property of the item. Here we are modifying the tags collection property by adding a new Tag object.
-
Add a new line of code to invoke the UpsertItemAsync method passing in both the item and the options:
response = await container.UpsertItemAsync(response.Resource, requestOptions: requestOptions);
-
Add a new line of code to print out the ETag of the newly updated item:
await Console.Out.WriteLineAsync($"New ETag:\t{response.ETag}");
-
Your
Main
method should now look like this:public static async Task Main(string[] args) { Database database = _client.GetDatabase(_databaseId); Container container = database.GetContainer(_containerId); ItemResponse<Food> response = await container.ReadItemAsync<Food>("21083", new PartitionKey("Fast Foods")); await Console.Out.WriteLineAsync($"Existing ETag:\t{response.ETag}"); ItemRequestOptions requestOptions = new ItemRequestOptions { IfMatchEtag = response.ETag }; response.Resource.tags.Add(new Tag { name = "Demo" }); response = await container.UpsertItemAsync(response.Resource, requestOptions: requestOptions); await Console.Out.WriteLineAsync($"New ETag:\t{response.ETag}"); }
-
Save all of your open editor tabs.
-
In the open terminal pane, enter and execute the following command:
dotnet run
-
Observe the output of the console application.
You should see that the value of the ETag property has changed. The ItemRequestOptions class helped us implement optimistic concurrency by specifying that we wanted the SDK to use the If-Match header to allow the server to decide whether a resource should be updated. The If-Match value is the ETag value to be checked against. If the ETag value matches the server ETag value, the resource is updated. If the ETag is no longer current, the server rejects the operation with an "HTTP 412 Precondition failure" response code. The client then re-fetches the resource to acquire the current ETag value for the resource.
-
Back in the
Main
method, add a new line of code to again update a property of the item:response.Resource.tags.Add(new Tag { name = "Failure" });
-
Add a new line of code to again invoke the UpsertItemAsync method passing in both the updated item and the same options as before:
response = await container.UpsertItemAsync(response.Resource, requestOptions: requestOptions);
The ItemRequestOptions instance has not been updated, so is still using the ETag value from the original object, which is now out of date so we should expect to now get an error.
-
Add error handling to the UpsertItemAsync call you just added by wrapping it with a try-catch and then output the resulting error message. The code should now look like this:
try { response = await container.UpsertItemAsync(response.Resource, requestOptions: requestOptions); } catch (Exception ex) { await Console.Out.WriteLineAsync($"Update error:\t{ex.Message}"); }
-
Save all of your open editor tabs.
-
In the open terminal pane, enter and execute the following command:
dotnet run
-
Observe the output of the console application.
You should see that the second update call fails because value of the ETag property has changed. The ItemRequestOptions class specifying the original ETag value as an If-Match header caused the server to decide to reject the update operation with an "HTTP 412 Precondition failure" response code.
If this is your final lab, follow the steps in Removing Lab Assets to remove all lab resources.