In this lab, you will use the .NET SDK to tune an Azure Cosmos DB request to optimize performance of your application.
Before you start this lab, you will need to create an Azure Cosmos DB database and collection that you will use throughout the lab. You will also use the Data Migration Tool to import existing data into your collection.
A JSON file has been provided that will contain a collection 50,000 students. You will use this file later to import documents into your collection.
- Download the transactions.json file and save it to your local machine.
You will now create a database and collection within your Azure Cosmos DB account.
-
On the left side of the portal, click the Resource groups link.
-
In the Resource groups blade, locate and select the COSMOSLABS Resource Group.
-
In the COSMOSLABS blade, select the Azure Cosmos DB account you recently created.
-
In the Azure Cosmos DB blade, locate and click the Overview link on the left side of the blade.
-
At the top of the Azure Cosmos DB blade, click the Add Collection button.
-
In the Add Collection popup, perform the following actions:
-
In the Database id field, select the Create new option and enter the value FinancialDatabase.
-
Ensure the Provision database throughput option is not selected.
-
In the Collection id field, enter the value TransactionCollection.
-
In the Storage capacity section, select the Unlimited option.
-
In the Partition key field, enter the value
/costCenter
. -
In the Throughput field, enter the value
10000
. -
Click the OK button.
-
-
Wait for the creation of the new database and collection to finish before moving on with this lab.
-
At the top of the Azure Cosmos DB blade, click the Add Collection button.
-
In the Add Collection popup, perform the following actions:
-
In the Database id field, select the Existing database option and then enter the value FinancialDatabase.
-
In the Collection id field, enter the value PeopleCollection.
-
In the Storage capacity section, select the Fixed-Size option.
-
In the Throughput field, enter the value
1000
. -
Click the OK button.
-
-
Wait for the creation of the new database and collection to finish before moving on with this lab.
The Data Migration Tool and .NET SDKs both require credentials to connect to your Azure Cosmos DB account. You will collect and store these credentials for use throughout the lab.
-
On the left side of the Azure Cosmos DB blade, locate the Settings section and click the Keys link.
-
In the Keys pane, record the values in the CONNECTION STRING, URI and PRIMARY KEY fields. You will use these values later in this lab.
*Finally, you will import the JSON documents contained in the transactions.json file you downloaded earlier in this lab. You will use the Data Migration Tool to import the JSON array stored in the transactions.json file from your local machine to your Azure Cosmos DB collection.
-
On your local machine, open the Azure Cosmos DB Data Migration Tool.
-
In the Welcome step of the tool, click the Next button to begin the migration wizard.
-
In the Source Information step of the tool, perform the following actions:
-
In the Import from list, select the JSON file(s) option.
-
Click the Add Files button.
-
In the Windows Explorer dialog that opens, locate and select the transactions.json file you downloaded earlier in this lab. Click the Open button to add the file.
-
Select the Decompress data checkbox.
-
Click the Next button.
-
-
In the Target Information step of the tool, perform the following actions:
-
In the Export to list, select the Azure Cosmos DB - Sequential record import (partitioned collection) option.
-
In the Connection String field, enter a newly constructed connection string replacing the placeholders with values from your Azure Cosmos DB account recorded earlier in this lab:
AccountEndpoint=[uri];AccountKey=[key];Database=[database name];
. Make sure you replace the [uri], [key], and [database name] placeholders with the corresponding values from your Azure Cosmos DB account. For example, if your uri ishttps://cosmosacct.documents.azure.com:443/
, your key isyFMff7GiFN7AaGPC2WehNcgnvt8kP2EbkLnuVNWYgKoFmQ5dYyZ94cBXd8wBgV0TCKzmIO3z2y8WoRkViL2waA==
and your database's name isFinancialDatabase
, then your connection string will look like this:AccountEndpoint=https://cosmosacct.documents.azure.com:443/;AccountKey=yFMff7GiFN7AaGPC2WehNcgnvt8kP2EbkLnuVNWYgKoFmQ5dYyZ94cBXd8wBgV0TCKzmIO3z2y8WoRkViL2waA==;Database=FinancialDatabase;
-
Click the Verify button to validate your connection string.
-
In the Collection field, enter the value TransactionCollection.
-
In the Collection Throughput field, enter the value
10000
. -
Click the Next button.
-
-
In the Advanced step of the tool, leave the existing options set to their default values and click the Next button.
-
In the Summary step of the tool, review your options and then click the Import button.
-
Wait for the import process to complete.
You will know that the tool has run successfully once it has transferred 50000 records and the progress bar's animation ends. This step can take two to five minutes.
-
Once the import process has completed, close the Azure Cosmos DB Data Migration Tool.
-
On your local machine, create a new folder that will be used to contain the content of your .NET Core project.
-
In the new folder, right-click the folder and select the Open with Code menu option.
Alternatively, you can run a command prompt 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 Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet new console --output .
This command will create a new .NET Core 2.1 project. The project will be a console project and the project will be created in the current directly since you used the
--output .
option. -
Visual Studio Code will most likely prompt you to install various extensions related to .NET Core or Azure Cosmos DB development. None of these extensions are required to complete the labs.
-
In the terminal pane, enter and execute the following command:
dotnet add package Microsoft.Azure.DocumentDB.Core --version 1.9.1
This command will add the Microsoft.Azure.DocumentDB.Core NuGet package as a project dependency. The lab instructions have been tested using the
1.9.1
version of this NuGet package. -
In the terminal pane, enter and execute the following command:
dotnet add package Bogus --version 22.0.8
This command will add the Bogus NuGet package as a project dependency. This library will allow us to quickly generate test data using a fluent syntax and minimal code. We will use this library to generate test documents to upload to our Azure Cosmos DB instance. The lab instructions have been tested using the
22.0.8
version of this NuGet package. -
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.
-
Click the π symbol to close the terminal pane.
-
Observe the Program.cs and [folder name].csproj files created by the .NET Core CLI.
-
Double-click the [folder name].csproj link in the Explorer pane to open the file in the editor.
-
Add a new PropertyGroup XML element to the project configuration within the Project element:
<PropertyGroup> <LangVersion>latest</LangVersion> </PropertyGroup>
-
Your new XML should look like this:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <LangVersion>latest</LangVersion> </PropertyGroup> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Bogus" Version="22.0.8" /> <PackageReference Include="Microsoft.Azure.DocumentDB.Core" Version="1.9.1" /> </ItemGroup> </Project>
-
Double-click the Program.cs link in the Explorer pane to open the file in the editor.
The DocumentClient class is the main "entry point" to using the SQL API in Azure Cosmos DB. We are going to create an instance of the DocumentClient class by passing in connection metadata as parameters of the class' constructor. We will then use this class instance throughout the lab.
-
Within the Program.cs editor tab, Add the following using blocks to the top of the editor:
using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Net; using System.Threading.Tasks; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; using Microsoft.Azure.Documents.Linq;
-
Locate the Program class and replace it with the following class:
public class Program { public static async Task Main(string[] args) { } }
-
Within the Program class, add the following lines of code to create variables for your connection information:
private static readonly Uri _endpointUri = new Uri(""); private static readonly string _primaryKey = ""; private static readonly string _databaseId = "FinancialDatabase"; private static readonly string _collectionId = "PeopleCollection";
-
For the
_endpointUri
variable, replace the placeholder value with the URI value from your Azure Cosmos DB account that you recorded earlier in this lab:For example, if your uri is
https://cosmosacct.documents.azure.com:443/
, your new variable assignment will look like this:private static readonly Uri _endpointUri = new Uri("https://cosmosacct.documents.azure.com:443/");
. -
For the
_primaryKey
variable, replace the placeholder value with the PRIMARY KEY value from your Azure Cosmos DB account that you recorded earlier in this lab:For example, if your primary key is
NAye14XRGsHFbhpOVUWB7CMG2MOTAigdei5eNjxHNHup7oaBbXyVYSLW2lkPGeKRlZrCkgMdFpCEnOjlHpz94g==
, your new variable assignment will look like this:private static readonly string _primaryKey = "NAye14XRGsHFbhpOVUWB7CMG2MOTAigdei5eNjxHNHup7oaBbXyVYSLW2lkPGeKRlZrCkgMdFpCEnOjlHpz94g==";
. -
Locate the Main method:
public static async Task Main(string[] args) { }
-
Within the Main method, add the following lines of code to author a using block that creates and disposes a DocumentClient instance:
using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { }
-
Locate the using block within the Main method:
using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { }
-
Add the following line of code to create a variable named
collectionLink
that references the self-link Uri for the collection:Uri collectionLink = UriFactory.CreateDocumentCollectionUri(_databaseId, _collectionId);
-
Your
Program
class definition should now look like this:public class Program { private static readonly Uri _endpointUri = new Uri("<your uri>"); private static readonly string _primaryKey = "<your key>"; private static readonly string _databaseId = "FinancialDatabase"; private static readonly string _collectionId = "PeopleCollection"; public static async Task Main(string[] args) { using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { Uri collectionLink = UriFactory.CreateDocumentCollectionUri(_databaseId, _collectionId); } } }
We will now execute build the application to make sure our code compiles successfully.
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet build
This command will build the console project.
-
Click the π symbol to close the terminal pane.
-
Close all open editor tabs.
Azure Cosmos DB returns various response headers that can give you more metadata about your request and what operations occured on the server-side. The .NET SDK exposes many of these headers to you as properties of the ResourceResponse<>
class.
-
Locate the using block within the Main method:
using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { }
-
After the last line of code in the using block, add a new line of code to create a new object and store it in a variable named doc:
object doc = new Bogus.Person();
The Bogus library has a special helper class (
Bogus.Person
) that will generate a fictional person with randomized properties. Here's an example of a fictional person JSON document:{ "Gender": 1, "FirstName": "Rosalie", "LastName": "Dach", "FullName": "Rosalie Dach", "UserName": "Rosalie_Dach", "Avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/mastermindesign/128.jpg", "Email": "[email protected]", "DateOfBirth": "1962-02-22T21:48:51.9514906-05:00", "Address": { "Street": "79569 Wilton Trail", "Suite": "Suite 183", "City": "Caramouth", "ZipCode": "85941-7829", "Geo": { "Lat": -62.1607, "Lng": -123.9278 } }, "Phone": "303.318.0433 x5168", "Website": "gerhard.com", "Company": { "Name": "Mertz - Gibson", "CatchPhrase": "Focused even-keeled policy", "Bs": "architect mission-critical markets" } }
-
Add a new line of code to invoke the CreateDocumentAsync method of the DocumentClient instance using the collectionLink and doc variables as parameters:
await client.CreateDocumentAsync(collectionLink, doc);
-
Your Main method should now look like this:
public static async Task Main(string[] args) { using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { Uri collectionLink = UriFactory.CreateDocumentCollectionUri(_databaseId, _collectionId); object doc = new Bogus.Person(); await client.CreateDocumentAsync(collectionLink, doc); } }
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the results of the console project.
You should see five aliases printed to the console window.
-
Click the π symbol to close the terminal pane.
-
Within the Program.cs editor tab, locate the Main method.
-
Within the Main method, locate the following line of code:
await client.CreateDocumentAsync(collectionLink, doc);
Replace that line of code with the following code:
ResourceResponse<Document> response = await client.CreateDocumentAsync(collectionLink, doc);
The
CreateDocumentAsync
method returns a ResourceResponse<> instance with various metadata about the create operation. -
After the last line of code in the using block, add a new line of code to print out the value of the RequestCharge property of the ResourceResponse<> instance:
await Console.Out.WriteLineAsync($"{response.RequestCharge} RUs");
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the results of the console project.
You should see the document creation operation use approximately 10 RUs.
-
Click the π symbol to close the terminal pane.
-
Return to the Azure Portal (http://portal.azure.com).
-
On the left side of the portal, click the Resource groups link.
-
In the Resource groups blade, locate and select the COSMOSLABS Resource Group.
-
In the COSMOSLABS blade, select the Azure Cosmos DB account you recently created.
-
In the Azure Cosmos DB blade, locate and click the Data Explorer link on the left side of the blade.
-
In the Data Explorer section, expand the FinancialDatabase database node and then observe select the PeopleCollection node.
-
Click the New SQL Query button at the top of the Data Explorer section.
-
In the query tab, replace the contents of the query editor with the following SQL query:
SELECT TOP 2 * FROM coll ORDER BY coll._ts DESC
This query will return the latest two documents added to your collection.
-
Click the Execute Query button in the query tab to run the query.
-
In the Results pane, observe the results of your query.
-
Return to the currently open Visual Studio Code editor containing your .NET Core project.
-
In the Visual Studio Code window, double-click the Program.cs file to open an editor tab for the file.
-
To view the RU charge for inserting a very large document, we will use the Bogus library to create a fictional family. To create a fictional family, we will generate two fictional parents and an array of 4 fictional children:
{ "Person": { ... }, "Relatives": { "Spouse": { ... }, "Children": [ { ... }, { ... }, { ... }, { ... } ] } }
Each property will have a Bogus-generated fictional person. This should create a large JSON document that we can use to observe RU charges.
-
Within the Program.cs editor tab, locate the Main method.
-
Within the Main method, locate the following line of code:
object doc = new Bogus.Person();
Replace that line of code with the following code:
object doc = new { Person = new Bogus.Person(), Relatives = new { Spouse = new Bogus.Person(), Children = Enumerable.Range(0, 4).Select(r => new Bogus.Person()) } };
This new block of code will create the large JSON object discussed above.
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the results of the console project.
You should see this new operation require far more RUs than the simple JSON document.
-
Click the π symbol to close the terminal pane.
-
Return to the Azure Portal (http://portal.azure.com).
-
On the left side of the portal, click the Resource groups link.
-
In the Resource groups blade, locate and select the COSMOSLABS Resource Group.
-
In the COSMOSLABS blade, select the Azure Cosmos DB account you recently created.
-
In the Azure Cosmos DB blade, locate and click the Data Explorer link on the left side of the blade.
-
In the Data Explorer section, expand the FinancialDatabase database node and then observe select the PeopleCollection node.
-
Click the New SQL Query button at the top of the Data Explorer section.
-
In the query tab, replace the contents of the query editor with the following SQL query:
SELECT * FROM coll WHERE IS_DEFINED(coll.Relatives)
This query will return the only document in your collection with a property named Children.
-
Click the Execute Query button in the query tab to run the query.
-
In the Results pane, observe the results of your query.
-
In the Azure Cosmos DB blade, locate and click the Data Explorer link on the left side of the blade.
-
In the Data Explorer section, expand the FinancialDatabase database node, expand the PeopleCollection node, and then select the Scale & Settings option.
-
In the Settings section, locate the Indexing Policy field and observe the current default indexing policy:
{ "indexingMode": "consistent", "automatic": true, "includedPaths": [ { "path": "/*", "indexes": [ { "kind": "Range", "dataType": "Number", "precision": -1 }, { "kind": "Range", "dataType": "String", "precision": -1 }, { "kind": "Spatial", "dataType": "Point" } ] } ], "excludedPaths": [] }
This policy will index all paths in your JSON document. This policy implements maximum percision (-1) for both numbers (max 8) and strings (max 100) paths. This policy will also index spatial data.
-
Replace the indexing policy with a new policy that removes the
/Relatives/*
path from the index:{ "indexingMode": "consistent", "automatic": true, "includedPaths": [ { "path":"/*", "indexes":[ { "kind": "Range", "dataType": "String", "precision": -1 }, { "kind": "Range", "dataType": "Number", "precision": -1 } ] } ], "excludedPaths": [ { "path":"/Relatives/*" } ] }
This new policy will exclude the
/Relatives/*
path from indexing effectively removing the Children property of your large JSON document from the index. -
Click the Save button at the top of the section to persist your new indexing policy and "kick off" a transformation of the collection's index.
-
Click the New SQL Query button at the top of the Data Explorer section.
-
In the query tab, replace the contents of the query editor with the following SQL query:
SELECT * FROM coll WHERE IS_DEFINED(coll.Relatives)
-
Click the Execute Query button in the query tab to run the query.
You will see immediately that you can still determine if the /Children path is defined.
-
In the query tab, replace the contents of the query editor with the following SQL query:
SELECT * FROM coll WHERE IS_DEFINED(coll.Relatives) ORDER BY coll.Relatives.Spouse.FirstName
-
Click the Execute Query button in the query tab to run the query.
This query will fail immediately since this property is not indexed.
-
Return to the currently open Visual Studio Code editor containing your .NET Core project.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the results of the console project.
You should see a difference in the number of RUs required to create this document. This is due to the indexer skipping the paths you excluded.
-
Click the π symbol to close the terminal pane.
-
Return to the Azure Portal (http://portal.azure.com).
-
On the left side of the portal, click the Resource groups link.
-
In the Resource groups blade, locate and select the COSMOSLABS Resource Group.
-
In the COSMOSLABS blade, select the Azure Cosmos DB account you recently created.
-
In the Azure Cosmos DB blade, locate and click the Data Explorer link on the left side of the blade.
-
In the Data Explorer section, expand the FinancialDatabase database node, expand the PeopleCollection node, and then select the Scale & Settings option.
-
In the Settings section, locate the Indexing Policy field and replace the indexing policy with a new policy:
{ "indexingMode": "consistent", "automatic": true, "includedPaths": [ { "path":"/*", "indexes":[ { "kind": "Range", "dataType": "String", "precision": -1 }, { "kind": "Range", "dataType": "Number", "precision": -1 } ] } ] }
This new policy removes the
/Relatives/*
path from the excluded path list so that the path can be indexed again. -
Click the Save button at the top of the section to persist your new indexing policy and "kick off" a transformation of the collection's index.
-
Click the New SQL Query button at the top of the Data Explorer section.
-
In the query tab, replace the contents of the query editor with the following SQL query:
SELECT * FROM coll WHERE IS_DEFINED(coll.Relatives) ORDER BY coll.Relatives.Spouse.FirstName
-
Click the Execute Query button in the query tab to run the query.
This query should now work. If you see an empty result set, this is because the indexer has not finished indexing all of the documents in the collection. Simply rerun the query until you see a non-empty result set.
-
Return to the currently open Visual Studio Code editor containing your .NET Core project.
-
In the Visual Studio Code window, double-click the Program.cs file to open an editor tab for the file.
-
Within the Program.cs editor tab, locate the Main method.
-
Within the Main method, locate the following line of code:
Uri collectionLink = UriFactory.CreateDocumentCollectionUri(_databaseId, _collectionId);
Replace that line of code with the following code:
Uri documentLink = UriFactory.CreateDocumentUri(_databaseId, _collectionId, "example.document");
Instead of having a Uri that references a collection, we will create a Uri that references a specific document. To create this Uri, you will need a third parameter specifying the unique string identifier for the document. In this example, our string id is
example.document
. -
Locate the following block of code:
object doc = new { Person = new Bogus.Person(), Relatives = new { Spouse = new Bogus.Person(), Children = Enumerable.Range(0, 4).Select(r => new Bogus.Person()) } };
Replace that line of code with the following code:
object doc = new { id: "example.document", FirstName: "Example", LastName: "Person" };
Here we are creating a new document that has an id property set to a value of
example.document
. -
Locate the following line of code:
ResourceResponse<Document> response = await client.CreateDocumentAsync(collectionLink, doc);
Replace that line of code with the following code:
ResourceResponse<Document> readResponse = await client.ReadDocumentAsync(documentLink);
We will now use the ReadDocumentAsync method of the DocumentClient class to retrieve a document using the unique id.
-
Locate the following line of code:
await Console.Out.WriteLineAsync($"{response.RequestCharge} RUs");
Replace that line of code with the following code:
await Console.Out.WriteLineAsync($"{readResponse.StatusCode}");
This will print out the status code that was returned as a response for your operation.
-
Your
Main
method should now look like this:public static async Task Main(string[] args) { using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { Uri documentLink = UriFactory.CreateDocumentUri(_databaseId, _collectionId, "example.document"); object doc = new { id = "example.document", FirstName = "Example", LastName = "Person" }; ResourceResponse<Document> readResponse = await client.ReadDocumentAsync(documentLink); await Console.Out.WriteLineAsync($"{readResponse.StatusCode}"); } }
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the exception message.
You should see that an exception was thrown since a document was not found that matches the specified id.
-
Click the π symbol to close the terminal pane.
-
Within the Main method, locate the following block of code:
ResourceResponse<Document> readResponse = await client.ReadDocumentAsync(documentLink); await Console.Out.WriteLineAsync($"{readResponse.StatusCode}");
Replace that line of code with the following code:
try { ResourceResponse<Document> readResponse = await client.ReadDocumentAsync(documentLink); await Console.Out.WriteLineAsync($"Success: {readResponse.StatusCode}"); } catch (DocumentClientException dex) { await Console.Out.WriteLineAsync($"Exception: {dex.StatusCode}"); }
This try-catch block will handle a DocumentClientException throw by printing out the status code related to the exception.
-
Your
Main
method should now look like this:public static async Task Main(string[] args) { using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { Uri documentLink = UriFactory.CreateDocumentUri(_databaseId, _collectionId, "example.document"); object doc = new { id = "example.document", FirstName = "Example", LastName = "Person" }; try { ResourceResponse<Document> readResponse = await client.ReadDocumentAsync(documentLink); await Console.Out.WriteLineAsync($"Success: {readResponse.StatusCode}"); } catch (DocumentClientException dex) { await Console.Out.WriteLineAsync($"Exception: {dex.StatusCode}"); } } }
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the exception message.
You should see a status code indicating that a document was not found. The catch block worked successfully.
-
Click the π symbol to close the terminal pane.
While you could manually implement upsert logic, the .NET SDK contains a useful method to implement this logic for you.
-
Within the Main method, locate the following line of code:
Uri documentLink = UriFactory.CreateDocumentUri(_databaseId, _collectionId, "example.document");
Replace that line of code with the following code:
Uri collectionLink = UriFactory.CreateDocumentCollectionUri(_databaseId, _collectionId);
-
Locate the following block of code:
try { ResourceResponse<Document> readResponse = await client.ReadDocumentAsync(documentLink); await Console.Out.WriteLineAsync($"Success: {readResponse.StatusCode}"); } catch (DocumentClientException dex) { await Console.Out.WriteLineAsync($"Exception: {dex.StatusCode}"); }
Replace that line of code with the following code:
ResourceResponse<Document> readResponse = await client.UpsertDocumentAsync(collectionLink, doc); await Console.Out.WriteLineAsync($"{readResponse.StatusCode}");
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the exception message.
Since we are "upserting" a document with a unique id, the server-side operation will be to create a new document. You should see the status code
Created
indicating that the create operation was completed successfully. -
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the exception message.
Since we are "upserting" a document with the same id, the server-side operation will be to update the existing document with the same id. You should see the status code
OK
indicating that the update operation was completed successfully. -
Click the π symbol to close the terminal pane.
First, you will use the .NET SDK to issue request beyond the assigned capacity for a container. Request unit consumption is evaluated at a per-second rate. For applications that exceed the provisioned request unit rate, requests are rate-limited until the rate drops below the provisioned throughput level. When a request is rate-limited, the server preemptively ends the request with an HTTP status code of 429 RequestRateTooLargeException
and returns the x-ms-retry-after-ms
header. The header indicates the amount of time, in milliseconds, that the client must wait before retrying the request. You will observe the rate-limiting of your requests in an example application.
-
Return to the Azure Portal (http://portal.azure.com).
-
On the left side of the portal, click the Resource groups link.
-
In the Resource groups blade, locate and select the COSMOSLABS Resource Group.
-
In the COSMOSLABS blade, select the Azure Cosmos DB account you recently created.
-
In the Azure Cosmos DB blade, locate and click the Data Explorer link on the left side of the blade.
-
In the Data Explorer section, expand the FinancialDatabase database node, expand the TransactionCollection node, and then select the Scale & Settings option.
-
In the Settings section, locate the Throughput field and update it's value to 1000.
This is the minimum throughput that you can allocate to an unlimited collection.
-
Click the Save button at the top of the section to persist your new throughput allocation.
-
Return to the currently open Visual Studio Code editor containing your .NET Core project.
-
In the Visual Studio Code window, right-click the Explorer pane and select the New File menu option.
-
Name the new file Transaction.cs . The editor tab will automatically open for the new file.
-
Paste in the following code for the
IInteraction
interface:public class Transaction { public double amount { get; set; } public bool processed { get; set; } public string paidBy { get; set; } public string costCenter { get; set; } }
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet build
This command will build the console project.
-
Click the π symbol to close the terminal pane.
-
Close all open editor tabs.
-
Double-click the Program.cs link in the Explorer pane to open the file in the editor.
-
Locate the following line of code that identifies the collection that will be used by the application:
private static readonly string _collectionId = "PeopleCollection";
Replace the line of code with the following line of code:
private static readonly string _collectionId = "TransactionCollection";
We will use a different collection for the next section of the lab.
-
Locate the using block within the Main method and delete any existing code:
public static async Task Main(string[] args) { using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { } }
-
Add the following code to the method to create an asynchronous connection:
await client.OpenAsync();
-
Add the following line of code to create a variable named
collectionLink
that is a reference (self-link) to an existing collection:Uri collectionLink = UriFactory.CreateDocumentCollectionUri(_databaseId, _collectionId);
-
Observe the code in the Main method.
For the next few instructions, we will use the Bogus library to create test data. This library allows you to create a collection of objects with fake data set on each object's property. For this lab, our intent is to focus on Azure Cosmos DB instead of this library. With that intent in mind, the next set of instructions will expedite the process of creating test data.
-
Add the following code to create a collection of
Transaction
instances:var transactions = new Bogus.Faker<Transaction>() .RuleFor(t => t.amount, (fake) => Math.Round(fake.Random.Double(5, 500), 2)) .RuleFor(t => t.processed, (fake) => fake.Random.Bool(0.6f)) .RuleFor(t => t.paidBy, (fake) => $"{fake.Name.FirstName().ToLower()}.{fake.Name.LastName().ToLower()}") .RuleFor(t => t.costCenter, (fake) => fake.Commerce.Department(1).ToLower()) .GenerateLazy(100);
As a reminder, the Bogus library generates a set of test data. In this example, you are creating 100 items using the Bogus library and the rules listed above. The GenerateLazy method tells the Bogus library to prepare for a request of 1000 items by returning a variable of type IEnumerable. Since LINQ uses deferred execution by default, the items aren't actually created until the collection is iterated.
-
Add the following foreach block to iterate over the
PurchaseFoodOrBeverage
instances:foreach(var transaction in transactions) { }
-
Within the
foreach
block, add the following line of code to asynchronously create a document and save the result of the creation task to a variable:ResourceResponse<Document> result = await client.CreateDocumentAsync(collectionLink, transaction);
The
CreateDocumentAsync
method of theDocumentClient
class takes in a self-link for a collection and an object that you would like to serialize into JSON and store as a document within the specified collection. -
Still within the
foreach
block, add the following line of code to write the value of the newly created resource'sid
property to the console:await Console.Out.WriteLineAsync($"Document Created\t{result.Resource.Id}");
The
ResourceResponse
type has a property namedResource
that can give you access to interesting data about a document such as it's unique id, time-to-live value, self-link, ETag, timestamp, and attachments. -
Your Main method should look like this:
public static async Task Main(string[] args) { using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { await client.OpenAsync(); Uri collectionLink = UriFactory.CreateDocumentCollectionUri(_databaseId, _collectionId); var transactions = new Bogus.Faker<Transaction>() .RuleFor(t => t.amount, (fake) => Math.Round(fake.Random.Double(5, 500), 2)) .RuleFor(t => t.processed, (fake) => fake.Random.Bool(0.6f)) .RuleFor(t => t.paidBy, (fake) => $"{fake.Name.FirstName().ToLower()}.{fake.Name.LastName().ToLower()}") .RuleFor(t => t.costCenter, (fake) => fake.Commerce.Department(1).ToLower()) .GenerateLazy(100); foreach(var transaction in transactions) { ResourceResponse<Document> result = await client.CreateDocumentAsync(collectionLink, transaction); await Console.Out.WriteLineAsync($"Document Created\t{result.Resource.Id}"); } } }
As a reminder, the Bogus library generates a set of test data. In this example, you are creating 100 items using the Bogus library and the rules listed above. The GenerateLazy method tells the Bogus library to prepare for a request of 500 items by returning a variable of type IEnumerable. Since LINQ uses deferred execution by default, the items aren't actually created until the collection is iterated. The foreach loop at the end of this code block iterates over the collection and creates documents in Azure Cosmos DB.
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
You should see a list of document ids associated with new documents that are being created by this tool.
-
Click the π symbol to close the terminal pane.
-
Back in the code editor tab, locate the following lines of code:
foreach(var transaction in transactions) { ResourceResponse<Document> result = await client.CreateDocumentAsync(collectionSelfLink, transaction); await Console.Out.WriteLineAsync($"Document Created\t{result.Resource.Id}"); }
Replace those lines of code with the following code:
List<Task<ResourceResponse<Document>>> tasks = new List<Task<ResourceResponse<Document>>>(); foreach(var transaction in transactions) { Task<ResourceResponse<Document>> resultTask = client.CreateDocumentAsync(collectionLink, transaction); tasks.Add(resultTask); } Task.WaitAll(tasks.ToArray()); foreach(var task in tasks) { await Console.Out.WriteLineAsync($"Document Created\t{task.Result.Resource.Id}"); }
We are going to attempt to run as many of these creation tasks in parallel as possible. Remember, our collection is configured at 1,000 RU/s.
-
Your Main method should look like this:
public static async Task Main(string[] args) { using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { await client.OpenAsync(); Uri collectionLink = UriFactory.CreateDocumentCollectionUri(_databaseId, _collectionId); var transactions = new Bogus.Faker<Transaction>() .RuleFor(t => t.amount, (fake) => Math.Round(fake.Random.Double(5, 500), 2)) .RuleFor(t => t.processed, (fake) => fake.Random.Bool(0.6f)) .RuleFor(t => t.paidBy, (fake) => $"{fake.Name.FirstName().ToLower()}.{fake.Name.LastName().ToLower()}") .RuleFor(t => t.costCenter, (fake) => fake.Commerce.Department(1).ToLower()) .GenerateLazy(100); List<Task<ResourceResponse<Document>>> tasks = new List<Task<ResourceResponse<Document>>>(); foreach(var transaction in transactions) { Task<ResourceResponse<Document>> resultTask = client.CreateDocumentAsync(collectionLink, transaction); tasks.Add(resultTask); } Task.WaitAll(tasks.ToArray()); foreach(var task in tasks) { await Console.Out.WriteLineAsync($"Document Created\t{task.Result.Resource.Id}"); } } }
As a reminder, the Bogus library generates a set of test data. In this example, you are creating 100 items using the Bogus library and the rules listed above. The GenerateLazy method tells the Bogus library to prepare for a request of 500 items by returning a variable of type IEnumerable. Since LINQ uses deferred execution by default, the items aren't actually created until the collection is iterated. The foreach loops at the end of this code block iterates over the collection and creates asynchronous tasks. Each asynchronous task will issue a request to Azure Cosmos DB. These requests are issued in parallel and should cause an exceptional scenario since your collection does not have enough assigned throughput to handle the volume of requests.
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
This query should execute successfully. We are only creating 100 documents and we most likely will not run into any throughput issues here.
-
Click the π symbol to close the terminal pane.
-
Back in the code editor tab, locate the following line of code:
.GenerateLazy(100);
Replace that line of code with the following code:
.GenerateLazy(5000);
We are going to try and create 5000 documents in parallel to see if we can hit out throughput limit.
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe that the application will crash after some time.
This query will most likely hit our throughput limit. You will see multiple error messages indicating that specific requests have failed.
-
Click the π symbol to close the terminal pane.
You will now tune your requests to Azure Cosmos DB by manipulating properties of the FeedOptions class in the .NET SDK.
-
Locate the using block within the Main method and delete any existing code:
public static async Task Main(string[] args) { using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { } }
-
Add the following code to the method to create an asynchronous connection:
await client.OpenAsync();
-
Add the following line of code to create a variable named
collectionLink
that is a reference (self-link) to an existing collection:Uri collectionLink = UriFactory.CreateDocumentCollectionUri(_databaseId, _collectionId);
-
Add the following lines of code to configure options for a query:
FeedOptions options = new FeedOptions { EnableCrossPartitionQuery = true, PopulateQueryMetrics = true };
-
Add the following line of code that will store a SQL query in a string variable:
string sql = "SELECT TOP 1000 * FROM c WHERE c.processed = true ORDER BY c.amount DESC";
This query will perform a cross-partition ORDER BY and only return the top 1000 out of 50000 documents.
-
Add the following line of code to create a document query instance:
IDocumentQuery<Document> query = client.CreateDocumentQuery<Document>(collectionLink, sql, options).AsDocumentQuery();
-
Add the following line of code to get the first "page" of results:
var result = await query.ExecuteNextAsync();
We will not enumerate the full result set. We are only interested in the metrics for the first page of results.
-
Add the following lines of code to print out all of the query metrics to the console:
foreach(string key in result.QueryMetrics.Keys) { await Console.Out.WriteLineAsync($"{key}\t{result.QueryMetrics[key]}"); }
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
You should see multiple metrics printed out in your console window. Pay close attention to the Total Query Execution Time, Request Charge and Retrieved Document Size metrics.
-
Click the π symbol to close the terminal pane.
-
Back in the code editor tab, locate the following line of code:
string sql = "SELECT TOP 1000 * FROM c WHERE c.processed = true ORDER BY c.amount DESC";
Replace that line of code with the following code:
string sql = "SELECT * FROM c WHERE c.processed = true";
This new query does not perform a cross-partition ORDER BY.
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
You should see a reduction in both the Request Charge and Total Query Execution Time values.
-
Back in the code editor tab, locate the following line of code:
string sql = "SELECT * FROM c WHERE c.processed = true";
Replace that line of code with the following code:
string sql = "SELECT * FROM c";
This new query does not filter the result set.
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
Observe the slight differences in the various metric values.
-
Back in the code editor tab, locate the following line of code:
string sql = "SELECT * FROM c";
Replace that line of code with the following code:
string sql = "SELECT c.id FROM c";
This new query does not filter the result set.
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
Observe the slight differences in the various metric values.
-
Locate the using block within the Main method and delete any existing code:
public static async Task Main(string[] args) { using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { } }
-
Add the following code to the method to create an asynchronous connection:
await client.OpenAsync();
-
Add the following line of code to create a variable named
collectionLink
that is a reference (self-link) to an existing collection:Uri collectionLink = UriFactory.CreateDocumentCollectionUri(_databaseId, _collectionId);
-
Add the following line of code to create a high-precision timer:
Stopwatch timer = new Stopwatch();
-
Add the following lines of code to configure options for a query:
FeedOptions options = new FeedOptions { EnableCrossPartitionQuery = true, MaxItemCount = 100, MaxDegreeOfParallelism = 1, MaxBufferedItemCount = 0 };
-
Add the following lines of code to write various values to the console window:
await Console.Out.WriteLineAsync($"MaxItemCount:\t{options.MaxItemCount}"); await Console.Out.WriteLineAsync($"MaxDegreeOfParallelism:\t{options.MaxDegreeOfParallelism}"); await Console.Out.WriteLineAsync($"MaxBufferedItemCount:\t{options.MaxBufferedItemCount}");
-
Add the following line of code that will store a SQL query in a string variable:
string sql = "SELECT * FROM c WHERE c.processed = true ORDER BY c.amount DESC";
This query will perform a cross-partition ORDER BY on a filtered result set.
-
Add the following line of code to start the timer:
timer.Start();
-
Add the following line of code to create a document query instance:
IDocumentQuery<Document> query = client.CreateDocumentQuery<Document>(collectionLink, sql, options).AsDocumentQuery();
-
Add the following lines of code to enumerate the result set.
while (query.HasMoreResults) { var result = await query.ExecuteNextAsync<Document>(); }
Since the results are paged, we will need to call the
ExecuteNextAsync
method multiple times in a while loop. -
Add the following line of code stop the timer:
timer.Stop();
-
Add the following line of code to write the timer's results to the console window:
await Console.Out.WriteLineAsync($"Elapsed Time:\t{timer.Elapsed.TotalSeconds}");
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
This initial query should take an unexpectedly long amount of time. This will require us to optimize our SDK options.
-
Back in the code editor tab, locate the following line of code:
FeedOptions options = new FeedOptions { EnableCrossPartitionQuery = true, MaxItemCount = 100, MaxDegreeOfParallelism = 1, MaxBufferedItemCount = 0 };
Replace that line of code with the following code:
FeedOptions options = new FeedOptions { EnableCrossPartitionQuery = true, MaxItemCount = 100, MaxDegreeOfParallelism = 5, MaxBufferedItemCount = 0 };
Setting the
MaxDegreeOfParallelism
property to a value of1
effectively creates eliminates parallelism. Here we "bump up" the parallelism to a value of5
. -
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
You shouldn't see a slight difference considering you now have some form of parallelism.
-
Back in the code editor tab, locate the following line of code:
FeedOptions options = new FeedOptions { EnableCrossPartitionQuery = true, MaxItemCount = 100, MaxDegreeOfParallelism = 5, MaxBufferedItemCount = 0 };
Replace that line of code with the following code:
FeedOptions options = new FeedOptions { EnableCrossPartitionQuery = true, MaxItemCount = 100, MaxDegreeOfParallelism = 5, MaxBufferedItemCount = -1 };
Setting the
MaxBufferedItemCount
property to a value of-1
effectively tells the SDK to manage this setting. -
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
Again, this should have a slight impact on your performance time.
-
Back in the code editor tab, locate the following line of code:
FeedOptions options = new FeedOptions { EnableCrossPartitionQuery = true, MaxItemCount = 100, MaxDegreeOfParallelism = 5, MaxBufferedItemCount = -1 };
Replace that line of code with the following code:
FeedOptions options = new FeedOptions { EnableCrossPartitionQuery = true, MaxItemCount = 100, MaxDegreeOfParallelism = -1, MaxBufferedItemCount = -1 };
Parallel query works by querying multiple partitions in parallel. However, data from an individual partitioned collect is fetched serially with respect to the query Setting the
MaxDegreeOfParallelism
property to a value of-1
effectively tells the SDK to manage this setting. Setting the MaxDegreeOfParallelism to the number of partitions has the maximum chance of achieving the most performant query, provided all other system conditions remain the same. -
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
Again, this should have a slight impact on your performance time.
-
Back in the code editor tab, locate the following line of code:
FeedOptions options = new FeedOptions { EnableCrossPartitionQuery = true, MaxItemCount = 100, MaxDegreeOfParallelism = -1, MaxBufferedItemCount = -1 };
Replace that line of code with the following code:
FeedOptions options = new FeedOptions { EnableCrossPartitionQuery = true, MaxItemCount = 500, MaxDegreeOfParallelism = -1, MaxBufferedItemCount = -1 };
We are increasing the amount of items returned per "page" in an attempt to improve the performance of the query.
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
You will notice that the query performance improved dramatically. This may be an indicator that our query was bottlenecked by the client computer.
-
Back in the code editor tab, locate the following line of code:
FeedOptions options = new FeedOptions { EnableCrossPartitionQuery = true, MaxItemCount = 500, MaxDegreeOfParallelism = -1, MaxBufferedItemCount = -1 };
Replace that line of code with the following code:
FeedOptions options = new FeedOptions { EnableCrossPartitionQuery = true, MaxItemCount = 1000, MaxDegreeOfParallelism = -1, MaxBufferedItemCount = -1 };
For large queries, it is recommended that you increase the page size up to a value of 1000.
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
By increasing the page size, you have sped up the query even more.
-
Back in the code editor tab, locate the following line of code:
FeedOptions options = new FeedOptions { EnableCrossPartitionQuery = true, MaxItemCount = 1000, MaxDegreeOfParallelism = -1, MaxBufferedItemCount = -1 };
Replace that line of code with the following code:
FeedOptions options = new FeedOptions { EnableCrossPartitionQuery = true, MaxItemCount = 1000, MaxDegreeOfParallelism = -1, MaxBufferedItemCount = 50000 };
Parallel query is designed to pre-fetch results while the current batch of results is being processed by the client. The pre-fetching helps in overall latency improvement of a query. MaxBufferedItemCount is the parameter to limit the number of pre-fetched results. Setting MaxBufferedItemCount to the expected number of results returned (or a higher number) allows the query to receive maximum benefit from pre-fetching.
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
This change should have decreased your query time by a small amount.
-
Click the π symbol to close the terminal pane.
-
Locate the using block within the Main method and delete any existing code:
public static async Task Main(string[] args) { using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { } }
-
Add the following code to the method to create an asynchronous connection:
await client.OpenAsync();
-
Add the following line of code to create a variable named
collectionLink
that is a reference (self-link) to an existing collection:Uri collectionLink = UriFactory.CreateDocumentCollectionUri(_databaseId, _collectionId);
-
Add the following line of code that will store a SQL query in a string variable:
string sql = "SELECT TOP 1 * FROM c WHERE c.id = 'example.document'";
This query will find a single document matching the specified unique id
-
Add the following line of code to create a document query instance:
IDocumentQuery<Document> query = client.CreateDocumentQuery<Document>(collectionLink, sql).AsDocumentQuery();
-
Add the following line of code to get the first page of results and then store them in a variable of type FeedResponse<>:
FeedResponse<Document> response = await query.ExecuteNextAsync<Document>();
We only need to retrieve a single page since we are getting the
TOP 1
documents from the collection. -
Add the following line of code to print out the value of the RequestCharge property of the ResourceResponse<> instance:
await Console.Out.WriteLineAsync($"{response.RequestCharge} RUs");
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
You should see the amount of RUs used to query for the document in your collection.
-
Click the π symbol to close the terminal pane.
-
Locate the using block within the Main method and delete any existing code:
public static async Task Main(string[] args) { using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { } }
-
Add the following code to the method to create an asynchronous connection:
await client.OpenAsync();
-
Add the following code to create a Uri referencing the document you wish to search for:
Uri documentLink = UriFactory.CreateDocumentUri(_databaseId, _collectionId, "example.document");
-
Add the following code to use the ReadDocumentAsync method of the DocumentClient class to retrieve a document using the unique id:
ResourceResponse<Document> response = await client.ReadDocumentAsync(documentLink);
-
Add the following line of code to print out the value of the RequestCharge property of the ResourceResponse<> instance:
await Console.Out.WriteLineAsync($"{response.RequestCharge} RUs");
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
You should see that it took fewer RUs to obtain the document directly if you have it's unique id.
-
Click the π symbol to close the terminal pane.
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 a document is updated. In this exercise, we will view the ETag property of a resource that is requested using the SDK.
-
Locate the using block within the Main method:
using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { }
-
Within the Main method, locate the following line of code:
await Console.Out.WriteLineAsync($"{response.RequestCharge} RUs");
Replace that line of code with the following code:
await Console.Out.WriteLineAsync($"ETag: {response.Resource.ETag}");
The ETag header and the current value are included in all response messages.
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
You should see an ETag for the document.
-
Enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
The ETag should remain unchanged since the document has not been changed.
-
Click the π symbol to close the terminal pane.
-
Locate the using block within the Main method:
using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { }
-
Within the Main method, locate the following line of code:
await Console.Out.WriteLineAsync($"{response.RequestCharge} RUs");
Replace that line of code with the following code:
await Console.Out.WriteLineAsync($"Existing ETag:\t{response.Resource.ETag}");
-
Within the using block, add a new line of code to create an AccessCondition instance that will use the ETag from the document and specify an If-Match header:
AccessCondition cond = new AccessCondition { Condition = response.Resource.ETag, Type = AccessConditionType.IfMatch };
-
Add a new line of code to update a property of the document using the SetPropertyValue method:
response.Resource.SetPropertyValue("FirstName", "Demo");
This line of code will modify a property of the document. Here we are modifying the FirstName property and changing it's value from Example to Demo.
-
Add a new line of code to create an instance of the RequestOptions class using the AccessCondition created earlier:
RequestOptions options = new RequestOptions { AccessCondition = cond };
-
Add a new line of code to invoke the ReplaceDocumentAsync method passing in both the document and the options:
response = await client.ReplaceDocumentAsync(response.Resource, options);
-
Add a new line of code to print out the ETag of the newly updated document:
await Console.Out.WriteLineAsync($"New ETag:\t{response.Resource.ETag}");
-
Your Main method should now look like this:
public static async Task Main(string[] args) { using (DocumentClient client = new DocumentClient(_endpointUri, _primaryKey)) { await client.OpenAsync(); Uri documentLink = UriFactory.CreateDocumentUri(_databaseId, _collectionId, "example.document"); ResourceResponse<Document> response = await client.ReadDocumentAsync(documentLink); await Console.Out.WriteLineAsync($"Existing ETag:\t{response.Resource.ETag}"); AccessCondition cond = new AccessCondition { Condition = response.Resource.ETag, Type = AccessConditionType.IfMatch }; response.Resource.SetPropertyValue("FirstName", "Demo"); RequestOptions options = new RequestOptions { AccessCondition = cond }; response = await client.ReplaceDocumentAsync(response.Resource, options); await Console.Out.WriteLineAsync($"New ETag:\t{response.Resource.ETag}"); } }
-
Save all of your open editor tabs.
-
In the Visual Studio Code window, right-click the Explorer pane and select the Open in Command Prompt menu option.
-
In the open terminal pane, enter and execute the following command:
dotnet run
This command will build and execute the console project.
-
Observe the output of the console application.
You should see that the value of the ETag property has changed. The AccessCondition 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.
-
Click the π symbol to close the terminal pane.
-
Close all open editor tabs.
-
Close the Visual Studio Code application.
-
Close your browser application.