Building an Application with C#, Cosmos DB, a NoSQL Document Database, and the SQL API

In this chapter, we will use Cosmos DB, the .NET Core SDK, the SQL API, and C# to code our first Cosmos DB application. We will focus on learning about many important aspects related to the SDK in order to easily build a first version of the application. We will use dynamic objects to start quickly and we will create a baseline that we will improve in a second version.

Understanding the requirements for the first version of an application

So far, we have been working with different web-based and GUI tools to interact with the Cosmos DB service and a document database with the SQL API. We have been using a single partition and the default indexing options. Now we will leverage our existing Cosmos DB knowledge to perform the different operations we learned with the Cosmos DB .NET Core SDK. In addition, we will work with a partition key and customized indexing options.

First, we will create an application that will work with dynamic documents without any schema by taking advantage of the dynamic keyword to create dynamic objects. The use of this keyword is one of a few possible approaches to interacting with Cosmos DB JSON documents in .NET Core and C#. The clear advantage of this approach is that we don't need to create a class that represents the documents just to perform a few operations with a collection. We will look at many common scenarios with the .NET Core SDK and then we will create a new version of the application that will use Plain Old CLR Objects

(POCOs)—that is, classes that represent the documents—and that will allow us to take full advantage of working with LINQ queries against documents. Thus, we must keep in mind that the first version of our application won't represent best practices, but it will allow us to easily dive deep into the .NET Core SDK.

We will use Visual Studio 2017 as a baseline for the examples. However, you can also run the examples in Visual Studio Code in any of its supported platforms. You can work with the Azure Cosmos DB emulator or the Cosmos DB cloud-based service. Remember that the use of cloudbased services will consume credits and charges might be billed based on the Azure subscription you have.

We will stay focused on the different tasks with a Cosmos DB database, and therefore, we will create a .NET Core 2 console application. Then, we will be able to use the improved version of this application as a baseline for other future applications that require interaction with Cosmos DB, such as a RESTful web API, an ASP.NET Core MVC web application, a mobile app, or a microservice.

We will work with a few documents that represent eSports competitions, each with a unique title. Each document will include details about the competition location, the platforms that are allowed, the games that the players will be able to play, the number of registered competitors, the competition status, and its date and time. If the competition is either in progress or finished, the document will include data about the first three positions—that is, the winners—with details about their scores and prizes.

Our first version of the application will perform the following tasks:

  • Create a new document database if it doesn't exist
  • Create a new document collection with specific options if it doesn't exist
  • Retrieve a document with a specific title
  • Insert a document related to a competition that has finished and has winners
  • Retrieve the number of documents with a specific title
  • Insert a document related to a competition that is scheduled and doesn't have winners yet
  • Retrieve a document related to a competition that is scheduled and update its date and its number of registered competitors
  • Retrieve and display the titles for all the scheduled competitions that have more than 200 registered competitors

Make sure you have an Azure Cosmos DB account created in Azure Portal or the Cosmos DB emulator properly installed before trying to execute any of the next examples. We will use the same account with the SQL API we have created in the previous chapters. However, remember that you can decide to use the emulator.

Understanding the main classes of the Cosmos DB SDK for .NET Core

The Microsoft.Azure.DocumentDB.Core NuGet package provides the client library for .NET Core to connect to Azure Cosmos DB through the SQL API. This package is essential for working with Cosmos DB in any .NET Core application. We will analyze some of the most important classes of Microsoft.Azure.DocumentDB.Core Version 2.0.0 before we start working on the first version of the application.

The following diagram shows the different resources that belong to a Cosmos DB account: a document database and a collection with the names of the class or classes of the Azure Cosmos DB client library, which will represent each resource below the resource name in bold. We will work with many of these classes in the first version of our application and in its next version. Note that the diagram is not a class diagram, and therefore, the arrows don't mean inheritance. The diagram shows the structure of the different resources and the classes that we will use to work with them:

The next table summarizes the information displayed in the previous diagram:

Namespace

Class name

Description

Microsoft.Azure.Documents.Client

DocumentClient

This class allows us to configure and execute requests against a specific account of the Cosmos DB service. In this case, we will only work with accounts created with the SQL API.

Microsoft.Azure.Documents

DatabaseAccount

This class represents a database account with the SQL API; that is, the container for document databases.

Microsoft.Azure.Documents

Database

This class represents a document database with the SQL API within a Cosmos DB database account.

Microsoft.Azure.Documents

DocumentCollection

This class represents a document collection (the container) within a document database.

Microsoft.Azure.Documents

Document

This class represents a JSON document that belongs to a document collection.

Microsoft.Azure.Documents

StoredProcedure

This class represents a stored procedure written with the server-side JavaScript API.

Microsoft.Azure.Documents

Trigger

This class represents a trigger written in JavaScript. The trigger can be either a pretrigger or a post-trigger.

Microsoft.Azure.Documents

Conflict

This class represents a version conflict resource.

The following classes inherit from the Microsoft.Azure.Documents.Resource class:

  • DatabaseAccount
  • Database
  • DocumentCollection
  • Document
  • StoredProcedure
  • Trigger
  • Conflict

The following lines show the definition for the Microsoft.Azure.Documents.Resource class:

using System; using Newtonsoft.Json;
namespace Microsoft.Azure.Documents
{
    public abstract class Resource : JsonSerializable
    {
        protected Resource();
        protected Resource(Resource resource);
        [JsonProperty(PropertyName = "id")]
         public virtual string Id { get; set; }
         [JsonProperty(PropertyName = "_rid")]
         public virtual string ResourceId { get; set; }
        [JsonProperty(PropertyName = "_self")]
         public string SelfLink { get; }
        [JsonIgnore]
        public string AltLink { get; set; }
        [JsonConverter(typeof(UnixDateTimeConverter))]
        [JsonProperty(PropertyName = "_ts")]
        public virtual DateTime Timestamp { get; internal set; }
        [JsonProperty(PropertyName = "_etag")]
         public string ETag { get; }
        public T GetPropertyValue<T>(string propertyName);
         public void SetPropertyValue(string propertyName, object propertyValue);
        public byte[] ToByteArray();
    }
}

The use of JsonProperty(PropertyName = attribute followed by the name of the JSON key and a closing bracket maps a specific JSON key to the C# property, which has a different name. For example, the _rid JSON key will be available in the ResourceId property in an instance of the Document class. We learned about many of these properties in Chapter 2, Getting Started with Cosmos DB Development and NoSQL Document Databases, in the Understanding the automatically generated key-value pairs section. Specifically, we analyzed the automatically generated key-value pairs for a new document inserted in a collection. In this case, the generalized resources have fewer automatically generated values than a document. All the previously enumerated classes that represent Cosmos DB resources inherit the following standard resource properties defined in the code shown for the Resource class:

Resource property

JSON key (Cosmos DB property)

Description

Id

id

This string defines id for the resource. Notice that this id is different to the resource id that Cosmos DB uses internally. We can combine the ids for different resources to generate a URI that allows us to easily identify a resource. This URI is stored in the AltLink property.

AltLink

Not available

This string provides a unique addressable URI for the resource that combines the IDs for the different resources to generate a URI. The value of the AltLink property is not available as a JSON key and is automatically generated by the Cosmos DB client. We can use this property instead of the methods that allow us to build the URI with the IDs.

ResourceId

_rid

This string defines the resource ID that Cosmos DB uses internally to identify and navigate through the document resource.

SelfLink

_self

This string provides a unique addressable URI for the resource. As previously learned, we can combine self links for different resources to generate a URI that allows us to easily identify a resource. This string isn't equivalent to the previously explained AltLink property.

Timestamp

_ts

Timestamp in C# provides the last date and time at which the resource was updated.

ETag

_etag

This string provides the entity tag.

In previous versions of the Cosmos DB SDK, we were forced to use a combination of self links to identify a resource. In the newest versions, we can also combine the ID for the different resources to address a resource such as a database, a collection, or a document. We will take advantage of the possibilities included in the newest versions.

Each class that represents a resource and inherits from the Resource class adds its own properties. However, all of them allow us to access the previously analyzed common properties.

Creating a .NET Core 2 application to interact with Cosmos DB

Now we will create a new multiplatform .NET Core 2 console app. We will install the necessary NuGet packages to work with the Cosmos DB SDK and make it easy to use a JSON configuration file for our application.

In Visual Studio, select File | New | Project and select Visual C# | .NET Core | Console App (.NET Core). Enter SampleApp1 for the project name. The code file for the sample is included in the learning_cosmos_db_04_01 folder in the dot_net_core_2_samples/SampleApp1/SampleApp1.sln file.

Install the NuGet packages and versions detailed in the next table. If there are newer versions available, you will have to verify the change log to make sure that there are no breaking changes. The sample has been tested with the specified versions:

Package name

Version

Microsoft.Azure.DocumentDB.Core

2.0.0

Microsoft.Extensions.Configuration

2.1.1

Microsoft.Extensions.Configuration.Json

2.1.1

As previously explained, the Microsoft.Azure.DocumentDB.Core package provides the client library for .NET Core to connect to Azure Cosmos DB through the SQL API. The Microsoft.Extensions.Configuration and Microsoft.Extensions.Configuration.Json packages provide a JSON key-value pair-based configuration that will allow us to easily configure the settings for the Azure Cosmos DB client.

Run the following commands within the Package Manager Console to install the previously enumerated packages:

Install-Package Microsoft.Azure.DocumentDB.Core -Version 2.0.0
Install-Package Microsoft.Extensions.Configuration -Version 2.1.1
Install-Package Microsoft.Extensions.Configuration.Json -Version 2.1.1

Now we will add a JSON configuration file for the console application with the necessary values to establish a connection with the Cosmos DB service and some additional values to specify which database and collection ids we will use to store documents. In Visual Studio, right-click on the project name (SampleApp1), select Add | New Item, and then select Visual C# Items | Web | Scripts | JavaScript JSON Configuration File. Enter configuration.json as the desired name.

Right-click on the recently added configuration.json file and select Properties. Select Copy if newer for the Copy to Output Directory property. This way, the configuration file will be copied to the output folder when we build the application.

Replace the contents of the new configuration.json file with the next lines. Make sure you replace the value specified for the enpointUrl key with the endpoint URL for the Azure Cosmos DB account you want to use for this example, and replace the value for the authorizationKey key with the read-write primary key. In this case, we will work with a new database named Competition, which will have a new collection named

Competitions1. You can also make changes to these values to fulfill your requirements. The databaseId and collectionId keys define the IDs for the database and the collection that the code is going to create and use. The code file for the sample is included in the learning_cosmos_db_04_01 folder in the dot_net_core_2_samples/SampleApp1/SampleApp1/configuration.json file:

// Development configuration values
{
  "CosmosDB": {
    // Replace with the endpoint URL for your Azure Cosmos DB account
    "endpointUrl": "https://example001.documents.azure.com:443/",
    // Replace with the read-write primary key for your Azure Cosmos DB account
    "authorizationKey": "Replace with the read-write primary key for your Azure Cosmos DB account",
    // Replace with your desired database id
    "databaseId": "Competition",
    // Replace with your desired collection id
    "collectionId": "Competitions1"
  }
}

If you have doubts about the values you want to use, read the Understanding URIs, read-write and read-only keys, and connection strings section in Chapter 2, Getting Started with Cosmos DB Development and NoSQL Document Databases.

Configuring a Cosmos DB client

Now we will write the code for the main method of our console application that will retrieve the necessary values from the previously coded JSON configuration file to configure an instance of the Microsoft.Azure.Documents.Client.DocumentClient class, which will allow us to write additional code that will perform requests to the Azure Cosmos DB service. We will be writing methods that perform additional tasks later, and we will use many code snippets to understand each task. We will analyze many of the possible ways of performing tasks with the Cosmos DB SDK.

Replace the code of the Program.cs file with the following contents, which declare the necessary using statements, the first lines of the new Program class, and the Main static method. The code file for the sample is included in the learning_cosmos_db_04_01 folder in the dot_net_core_2_samples/SampleApp1/SampleApp1/Program.cs file:

namespace SampleApp1
{
    using Microsoft.Azure.Documents;
    using Microsoft.Azure.Documents.Client;
    using Microsoft.Azure.Documents.Linq;
    using Microsoft.Extensions.Configuration;
    using System;
    using System.Linq;
    using System.Threading.Tasks;
    public class Program
    {
        private static string databaseId;
        private static string collectionId;
        private static DocumentClient client;
        public static void Main(string[] args)
        {
            var configurationBuilder = new ConfigurationBuilder();
             configurationBuilder.AddJsonFile("configuration.json",

optional: false, reloadOnChange: false); var configuration = configurationBuilder.Build(); string endpointUrl = configuration["CosmosDB:endpointUrl"]; string authorizationKey = configuration["CosmosDB:authorizationKey"]; databaseId = configuration["CosmosDB:databaseId"]; collectionId = configuration["CosmosDB:collectionId"]; try { using (client = new DocumentClient(new Uri(endpointUrl), authorizationKey)) { CreateAndQueryDynamicDocumentsAsync().Wait(); } } catch (DocumentClientException dce) { var baseException = dce.GetBaseException(); Console.WriteLine( $"DocumentClientException occurred. Status code: {dce.StatusCode}; Message: {dce.Message}; Base exception message: {baseException.Message}"); } catch (Exception e) { var baseException = e.GetBaseException(); Console.WriteLine( $"Exception occurred. Message: {e.Message}; Base exception message: {baseException.Message}"); } finally { Console.WriteLine("Press any key to exit the console application."); Console.ReadKey(); }

The Program class declares the following three static properties, which the different methods will use to perform tasks with the Cosmos DB service:

  • databaseId: The Cosmos DB document database ID
  • collectionId: The collection ID
  • client: An instance of the DocumentClient class that will allow us to perform requests to the Azure Cosmos DB service

The Main static method creates an instance of the Microsoft.Extensions.Configuration.ConfigurationBuilder class named configurationBuilder. Then, the code calls that configurationBuilder.AddJsonFile method to add the previously created configuration.json file as a configuration provider with the key-value pairs for our work with Cosmos DB. The next line calls configurationBuild.Build to make all the key-value pairs available in the configuration object.

Then, the next lines retrieve the endpointUrl, authorizationId, databaseId, and collectionId values from the configuration object that grabs these values from the configuration.json file. The databaseId and collectionId values are stored in static fields with the same names and many methods will use them to perform operations on the document database and the collection.

Then, the code creates a new DocumentClient instance within a using statement and saves it in the client field. The following lines show the code that creates a URI instance with the endpointUrl value and passes it as a parameter with the authorizationKey to the DocumentClient constructor. The code within the using block calls the CreateAndQueryDynamicDocumentsAsync method, chained to the call to the Wait method, that makes it wait for the task returned by the asynchronous method to finish. Note that the Wait method is necessary because we have created a console application and we used the default build settings that work with C# 7.0. We could also take advantage of new features included in C# 7.1, but they would require additional steps to simplify just one line of code. We will code the CreateAndQueryDynamicDocumentsAsync method later, and this method will call many asynchronous methods that will have the databaseId, collectionId, and client fields with their appropriate values and instances to perform operations with the Cosmos DB service. Once the method finishes its execution, the resources for DocumentClient are cleaned up:

using (client = new DocumentClient(new Uri(endpointUrl), authorizationKey))

{
    CreateAndQueryDynamicDocumentsAsync().Wait();
}

The preceding code is enclosed in a try...catch...finally block that catches any DocumentClientException exceptions and displays the StatusCode and Message property values for this type of exception. In addition, it displays the Message property value for the base exception. Whenever an operation related to Cosmos DB fails, DocumentClientException will be thrown, which will be captured so we can see the detailed message in the console.

In addition, the code catches any exception that is not an instance of DocumentClientException and prints details about it and its base exception.

The code in the finally block asks the user to press any key to exit the console application in order to make it possible to read all the messages displayed in the console before the window is closed.

First, we will code all the static asynchronous methods that the CreateAndQueryDynamicDocumentsAsync static method will call and we will analyze them. Then, we will code the CreateAndQueryDynamicDocumentsAsync static method.

Creating or retrieving a document database

The following lines declare the code for the RetrieveOrCreateDatabaseAsync asynchronous static method, which creates a new document database in the Cosmos DB account if a database with Id equal to the value stored in the databaseId field doesn't exist. Add the following lines to the existing code of the Program.cs file. The code file for the sample is included in the learning_cosmos_db_04_01 folder in the dot_net_core_2_samples/SampleApp1/SampleApp1/Program.cs file:

private static async Task<Database> RetrieveOrCreateDatabaseAsync()
{
    // Create a new document database if it doesn't exist
    var databaseResponse = await client.CreateDatabaseIfNotExistsAsync(
        new Database
        {
            Id = databaseId,
        });
    switch (databaseResponse.StatusCode)     {
         case System.Net.HttpStatusCode.Created:
            Console.WriteLine($"The database {databaseId} has been created.");
             break;
         case System.Net.HttpStatusCode.OK:
            Console.WriteLine($"The database {databaseId} has been retrieved.");
             break;
    }
    return databaseResponse.Resource;
}

The code calls the client.CreateDatabaseIfNotExistsAsync asynchronous method with a new Database instance with its Id set to the databaseId value with no specific request options. If a document database with the specified Id value already exists, the method retrieves the database resource. Otherwise, the method creates a new database with the provided Id. Hence, the first time this method is executed, it will create a database with the provided Id and the default provisioning options because the code doesn't specify any values for the optional options. The second time this method is executed, it will just retrieve the existing database resource.

The client.CreateDatabaseIfNotExistsAsync method returns a ResourceResponse<Database> instance, which the code saves in the databaseResponse variable. The created or retrieved database resource is available in the databaseResource.Resource property of this instance. In this case, the Resource property is of the previously explained Database type.

The responses from the create, read, update, and delete operations on any Cosmos DB resource will return the resource response wrapped in a ResourceResponse instance.

The databaseResource instance has many properties that provide the request unit consumed by the performed activity in the RequestCharge property. The code checks the value of the HTTP status code available in the databaseResponse.StatusCode property to determine whether the database has been created or retrieved. If this property is equal to the HTTP 201 created status (System.Net.HttpStatusCode.Created), it means that it was necessary to create the database because it didn't exist. If this property is equal to the HTTP 200 OK status (System.Net.HttpStatusCode.OK), it means that the database existed and it was retrieved.

Finally, the method returns the Database instance stored in the databaseResponse.Resource property.

If something goes wrong, the catch block defined in the Main method will capture any DocumentClientException or Exception instances and will display their details. For example, if a key is invalid, the connection won't be established with the Azure Cosmos DB service and the exception will be captured.

Querying and creating document collections

The following lines declare the code for the CreateCollectionIfNotExistsAsync asynchronous static method, which creates a new document collection if a collection with id equal to the value stored in the collectionId field doesn't exist in the database. Add the following lines to the existing code of the Program.cs file. The code file for the sample is included in the learning_cosmos_db_04_01 folder in the dot_net_core_2_samples/SampleApp1/SampleApp1/Program.cs file:

private static async Task<DocumentCollection>
CreateCollectionIfNotExistsAsync()
{
    var databaseUri = UriFactory.CreateDatabaseUri(databaseId);
    DocumentCollection documentCollectionResource;
    var isCollectionCreated = await client.CreateDocumentCollectionQuery(databaseUri)
        .Where(c => c.Id == collectionId)
        .CountAsync() == 1;     if (isCollectionCreated)
    {
        Console.WriteLine($"The collection {collectionId} already exists.");
        var documentCollectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, collectionId);
        var documentCollectionResponse = await client.ReadDocumentCollectionAsync(documentCollectionUri);
        documentCollectionResource = documentCollectionResponse.Resource;
    }     else
    {
        var documentCollection = new DocumentCollection
        {
            Id = collectionId,
        };
        documentCollection.PartitionKey.Paths.Add("/location/zipCode");
        var uniqueKey = new UniqueKey();         uniqueKey.Paths.Add("/title");
        documentCollection.UniqueKeyPolicy.UniqueKeys.Add(uniqueKey);
        var requestOptions = new RequestOptions
        {
            OfferThroughput = 1000,
        };
        var collectionResponse = await client.CreateDocumentCollectionAsync(
            databaseUri,             documentCollection,             requestOptions);
        if (collectionResponse.StatusCode == System.Net.HttpStatusCode.Created)
        {
            Console.WriteLine($"The collection {collectionId} has been created.");
         }
        documentCollectionResource = collectionResponse.Resource;     }
    return documentCollectionResource; }

The code doesn't use the easiest mechanism to create a collection when it doesn't exist because we will analyze how we can query the document collections for a database. Our goal is to learn about many possibilities offered by the SDK that will enable us to develop many different kinds of applications that work with Cosmos DB. However, it is very important to notice that the code for this method could be simplified by calling the CreateDocumentCollectionIfNotExistsAsync method.

First, the code calls the UriFactory.CreateDatabaseUri method with databaseId as its argument and saves the result in the databaseUri variable. This method will return a Uri instance with the URI for the database ID received as an argument. The code will use this URI to easily address the database resource in which we have to perform operations.

Note that the UriFactory.CreateDatabaseUri method requires the database ID to build the Uri instance and doesn't require any query to the database. However, of course, we must be sure that the database resource with the specified ID already exists before using the generated URI.

The next line declares the documentCollectionResource variable as a DocumentCollection instance. The code will end up returning this variable.

Then, the code creates a LINQ query with a call to the asynchronous client.CreateDocumentCollectionQuery method with databaseUri as an argument. This counts the number of collections whose Id is equal to collectionId with an asynchronous execution due to the usage of the chained CountAsync method. If the results of this query on the collections for the database is 1, it means that the collection already exists.

The following lines show the code that builds the LINQ query and stores the results of the Boolean expression in the isCollectionCreated variable:

var isCollectionCreated = await
client.CreateDocumentCollectionQuery(databaseUri)
    .Where(c => c.Id == collectionId)
    .CountAsync() == 1;

We can easily query the existing collections in a document database by chaining LINQ expressions to the call to the CreateDocumentCollectionQuery method. In this case, we are always working with asynchronous methods. If we used the Count method instead of CountAsync, the query would have a synchronous execution. We will always use the asynchronous methods in the examples.

If the collection exists, the code calls the UriFactory.CreateDocumentCollectionUri method with databaseId and collectionId as its arguments and saves the result in the documentCollectionUri variable. This method will return a Uri instance with the URI for document collection, generated with the combination of the database ID and the collection ID received as arguments. The code will use this URI to easily address the document collection resource we want to retrieve. The next line that is executed if the collection already exists calls the client.ReadDocumentCollectionAsync method with documentCollectionUri as an argument to retrieve the collection resource with the specified URI. This method returns a ResourceResponse<DocumentCollection> instance that the code saves in the documentCollectionResponse variable. The retrieved document collection resource is available in the documentCollectionResponse.Resource property of this instance, which is saved in the previously declared documentCollectionResource variable. In this case, the Resource property is of the previously explained DocumentCollection type.

We could have retrieved the DocumentCollection instance with a different version of the previously explained LINQ query. However, our goal is to explore different things we can do with the Cosmos DB SDK. In fact, whenever we know the database and collection ids and we want to retrieve a DocumentCollection instance, the most efficient way to do so is by calling the previously explained ReadDocumentCollectionAsync method.

If the collection doesn't exist, the code creates a new collection within the database whose ID is equal to databaseId. In order to do this, it is necessary to specify the desired settings for the collections that we configured in the Azure portal with C# code. The next line in the else block creates a new DocumentCollection instance with its Id set to the collectionId value and saves it in the documentCollection variable. Then, the code specifies the desired partition key and the desired unique key policy for the new collection. The call to the documentCollection.PartitionKey.Paths.Add method with location/zipCode as an argument adds the zipCode key of the location sub-document as the desired partition key for the collection to be created. This way, our documents will be partitioned by the ZIP code in which the competition is located.

Then, the code creates a new UniqueKey instance, saves it in the uniqueKey variable, and calls the uniqueKey.Paths.Add method with /title as an argument to specify the desired unique key path to the title key. Then, the call to the documentCollection.UniqueKeyPolicy.UniqueKeys.Add method with uniqueKey as an argument adds this instance to the unique key policies for the collection to be created. This way, we will make sure that Cosmos DB won't allow us to have two competitions with the same title.

The next line creates a new RequestOptions instance with its OfferThroughput property set to 1000 and saves it in the X variable. This way, we indicate that we want a reserved throughput of 1,000 request units per second for the collection.

The next line calls the client.CreateDocumentCollectionAsync method with the following arguments:

  • databaseUri: The Uri instance for the database that will hold the new collection.
  • documentCollection: The DocumentCollection instance with the id, partition key, and unique key definitions for the collection. In this case, we are using the default indexing options. We will dive deep into other indexing options later.
  • requestOptions: The RequestOptions instance with the desired reserved throughput for the collection.

The call to the client.CreateDocumentCollectionAsync method returns a ResourceResponse<DocumentCollection> instance, which the code saves in the collectionResponse variable. The created document collection resource is available in the collectionResponse.Resource property of this instance that is saved in the previously defined documentCollectionResource variable. In this case, the Resource property is of the previously explained DocumentCollection type.

The code checks the value of the HTTP status code available in the collectionResponse.StatusCode property to determine whether the collection has been created and displays a message if this property is equal to the HTTP 201 created status (System.Net.HttpStatusCode.Created).

Finally, the method returns the documentCollectionResource variable with the DocumentCollection instance that was either retrieved or created.

Retrieving a document with an asynchronous query

The following lines declare the code for the GetCompetitionByTitle asynchronous static method, which builds a query to retrieve a competition with a specific title from the document collection. The code takes advantage of the possibilities offered by the SDK to limit the number of results returned by the query and execute the query with an asynchronous execution. The code adds complexity to work with an asynchronous execution. We don't want to run a query with a synchronous execution in our examples. Add the following lines to the existing code of the Program.cs file. The code file for the sample is included in the learning_cosmos_db_04_01 folder in the dot_net_core_2_samples/SampleApp1/SampleApp1/Program.cs file:

private static async Task<dynamic> GetCompetitionByTitle(string competitionTitle)
{
    // Build a query to retrieve a document with a specific title
     var collectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, collectionId);
    var documentQuery = client.CreateDocumentQuery(collectionUri,
        $"SELECT * FROM Competitions c WHERE c.title = '{competitionTitle}'",
         new FeedOptions()
        {
            EnableCrossPartitionQuery = true,
            MaxItemCount = 1,
        })
        .AsDocumentQuery();
    while (documentQuery.HasMoreResults)
    {
        foreach (var competition in await documentQuery.ExecuteNextAsync())
        {
            Console.WriteLine(
                 $"The document with the following title exists: {competitionTitle}");
            Console.WriteLine(competition);
            return competition;
        }
    }
    // No matching document found
    return null; 
}

First, the code calls the UriFactory.CreateDocumentCollectionUri method with databaseId and collectionId as its arguments and saves the result in the collectionUri variable. This method will return a Uri instance with the URI for the document collection. The code will use this URI to easily address the document collection resource in which we have to run a query. We must be sure that the database resource with the specified ID already exists before using the generated URI.

The next line calls the client.CreateDocumentQuery method to create a query with documents with the following arguments:

  • collectionUri: The Uri instance for the document collection whose documents we want to query.
  • $"SELECT * FROM Competitions c WHERE c.title = '{competitionTitle}'": A string with the SQL API query to retrieve the competition whose title matches the string received in the competitionTitle parameter.
  • new FeedOptions() { EnableCrossPartitionQuery = true,MaxItemCount = 1 }: A new FeedOptions instance that specifies that we want to enable the query that sends more than one request because its scope is not limited to a single partition key value. The query will check competitions whose location might have different zipCode values, and therefore, we assign true to the EnableCrossPartitionQuery property. In addition, we assign 1 to the MaxItemCount property because we want a maximum of one result each time we perform the enumeration operation by calling the asynchronous ExecuteNextAsync method.

The client.CreateDocumentQuery method returns a System.Linq.IQueryable<dynamic> object, which the code converts to Microsoft.Azure.Documents.Linq.IDocumentQuery<dynamic> by chaining a call to the AsDocumentQuery method. The IDocumentQuery<dynamic> object supports pagination and asynchronous execution and it is saved in the documentQuery variable. In this case, pagination is not very important because we will always have a maximum of one document that matches the criteria. Remember that we enforce a unique title value for the documents.

At this point, the query hasn't been executed. The use of the AsDocumentQuery method enables the code to access the HasMoreResults bool property in a while loop that makes calls to the asynchronous ExecuteNextAsync method to retrieve more results as long as they are available. The first time the documentQuery.HasMoreResults property is evaluated, its value is true. However, no query is executed yet. Hence, the true value indicates that we must make a call to the documentQuery.ExecuteNextAsync asynchronous method to retrieve the first resultset with a maximum number of items equal to the MaxItemCount value specified for the FeedOptions instance. After the code calls the documentQuery.ExecuteNextAsync method for the first time, the value of the documentQuery.HasMoreResults property will be updated to indicate whether another call to the documentQuery.ExecuteNextAsync method is necessary because another resultset is available.

In this case, the loop will execute the documentQuery.ExecuteNextAsync asynchronous method once because we expect only one item in the resultset if a competition with the specified title exists. However, the code uses the loop to demonstrate how the queries that retrieve documents are usually executed, and we can use the code as a baseline for other queries. In this case, we don't load pages at a different time, and therefore, we don't work with a continuation token.

A foreach loop iterates the IEnumerable<dynamic> object provided by the FeedResponse<dynamic> object returned by the documentQuery.ExecuteNextAsync asynchronous method, which enumerates the results of the appropriate page of the execution of the query. Each retrieved competition is a dynamic object that represents the document retrieved from the document collection with the query. If there is a match, the code in the foreach loop will display the retrieved document; that is, the competition that matches the title and the method will return this dynamic object. Otherwise, the method will return null.

Inserting documents that represent competitions

Now we will write two methods that insert different documents that represent competitions. First, we will code a method that works with a dynamic object to insert the following JSON document that represents the first competition that has already finished. Note that the dateTime value for the document will be calculated to be 50 days before today:

{
    "id": "1",
    "title": "Crowns for Gamers - Portland 2018",
    "location": {
        "zipCode": "90210",
        "state": "CA"
    },
    "platforms": [
        "PS4",
        "XBox",
        "Switch"
    ],
    "games": [
        "Fortnite",
        "NBA Live 19"
    ],
    "numberOfRegisteredCompetitors": 80,
    "numberOfCompetitors": 60,
    "numberOfViewers": 300,
    "status": "Finished",
    "dateTime": "2018-07-23T01:25:11.0085577Z",
    "winners": [
        {
            "player": {
                "nickName": "EnzoTheGreatest",
                "country": "Italy",
                "city": "Rome"
            },
            "position": 1,
            "score": 7500,
            "prize": 1500
        },
        {
            "player": {
                "nickName": "NicoInGamerLand",
                "country": "Argentina",
                "city": "Buenos Aires"
            },
            "position": 2,
            "score": 6500,
            "prize": 750
        },
        {
            "player": {
                "nickName": "KiwiBoy",
                "country": "New Zealand",
                "city": "Auckland"
            },
            "position": 3,
            "score": 3500,
            "prize": 250
        }
    ],
}

The following lines declare the code for the InsertCompetition1 asynchronous static method, which receives the desired ID, title, and location ZIP code for the competition and inserts a new document in the document collection. Notice that, in this case, we are working with dynamic objects, and don't forget that we will create a new version of the application to use POCOs. Add the following lines to the existing code of the Program.cs file. The code file for the sample is included in the learning_cosmos_db_04_01 folder in the dot_net_core_2_samples/SampleApp1/SampleApp1/Program.cs file:

private static async Task<Document> InsertCompetition1(string competitionId,
    string competitionTitle,
    string competitionLocationZipCode)
{
    // Insert a document related to a competition that has finished and has winners
    var collectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, collectionId);
    var documentResponse = await client.CreateDocumentAsync(collectionUri, new
     {
        id = competitionId,
         title = competitionTitle,
        location = new
        {
            zipCode = competitionLocationZipCode,
            state = "CA",
        },
        platforms = new[]
        {
                "PS4", "XBox", "Switch"
        },         games = new[]
        {
                "Fortnite", "NBA Live 19"
        },
        numberOfRegisteredCompetitors = 80,
         numberOfCompetitors = 60,
         numberOfViewers = 300,
         status = "Finished",
         dateTime = DateTime.UtcNow.AddDays(-50),
        winners = new[]
        {                 new                 {
                    player = new
                    {
                        nickName = "EnzoTheGreatest",
                         country = "Italy",
                         city = "Rome"
                    },                     position = 1,
                     score = 7500,
                     prize = 1500,
                },
                 New
                 {
                    player = new
                    {
                        nickName = "NicoInGamerLand",
                         country = "Argentina",
                         city = "Buenos Aires"
                    },
                     position = 2,
                     score = 6500,
                     prize = 750
                },
                 New
                 {
                    player = new
                    {
                        nickName = "KiwiBoy",
                         country = "New Zealand",
                         city = "Auckland"
                    },
                     position = 3,
                     score = 3500,
                     prize = 250
                }
        },
    });
    if (documentResponse.StatusCode == System.Net.HttpStatusCode.Created)
    {
        Console.WriteLine($"The competition with the title {competitionTitle} has been created.");
    }
    return documentResponse.Resource;
 }

The first line calls the UriFactory.CreateDocumentCollectionUri method with databaseId and collectionId as its arguments and saves the result in the collectionUri variable. The next line calls the client.CreateDocumentAsync asynchronous method to request Cosmos DB to create a document with collectionUri and a new dynamic object that we want to serialize to JSON and insert as a document in the specified document collection.

The call to the client.CreateDocumentCollectionAsync method returns a ResourceResponse<Document> instance, which the code saves in the documentResponse variable. The created document resource is available in the documentResponse.Resource property of this instance. In this case, the Resource property is of the previously explained Document type.

The code checks the value of the HTTP status code available in the documentResponse.StatusCode property to determine whether the document has been created and displays a message if this property is equal to the HTTP 201 created status code (System.Net.HttpStatusCode.Created).

Finally, the method returns the documentResponse.Resource variable with the Document instance that was created.

Now we will code a method that works with another dynamic object to insert the following JSON document, which represents a second competition that is scheduled and hasn't started yet. Note that the dateTime value for the document will be calculated to be 50 days from now:

{
    "id": "2",
    "title": "Defenders of the crown - San Diego 2018",
    "location": {
        "zipCode": "92075",
        "state": "CA"
    },
    "platforms": [
        "PC",
        "PS4",
        "XBox"
    ],
    "games": [
        "Madden NFL 19",
        "Fortnite"
    ],
    "numberOfRegisteredCompetitors": 160,
    "status": "Scheduled",
    "dateTime": "2018-10-31T01:56:49.6411125Z",
}

The following lines declare the code for the InsertCompetition2 asynchronous static method, which is very similar to the previously explained InsertCompetition1 method. The only difference is that the dynamic object passed as an argument to the client.CreateDocumentAsync asynchronous method is different. Of course, we can generalize the insert method. However, we will do this in the second version of the application, which works with POCOs instead of using dynamic objects. Add the following lines to the existing code of the Program.cs file. The code file for the sample is included in the learning_cosmos_db_04_01 folder in the dot_net_core_2_samples/SampleApp1/SampleApp1/Program.cs file:

        private static async Task<Document> InsertCompetition2(string
competitionId,
            string competitionTitle,
            string competitionLocationZipCode)
        {
            // Insert a document related to a competition that is scheduled
            // and doesn't have winners yet
            var collectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, collectionId);
            var documentResponse = await client.CreateDocumentAsync(collectionUri, new
            {
                id = competitionId,
                 title = competitionTitle,
                location = new
                {
                    zipCode = competitionLocationZipCode,
                    state = "CA",
                },
                platforms = new[]
                {
                        "PC", "PS4", "XBox"
                },
                games = new[]
                {
                        "Madden NFL 19", "Fortnite"
                },
                numberOfRegisteredCompetitors = 160,
                status = "Scheduled",
                dateTime = DateTime.UtcNow.AddDays(50),
             });
            if (documentResponse.StatusCode == System.Net.HttpStatusCode.Created)
            {
                Console.WriteLine($"The competition with the title {competitionTitle} has been created.");
            }
            return documentResponse.Resource;
         }

We are working with error-prone dynamic objects. For example, if we have a typo and we write gamess instead of games in the dynamic object for the second competition, our code would generate a document that has a gamess key and another document that has a games key. Such a typo wouldn't cause a build error and we would only find out about the problem if we inspected the created documents. Hence, it is very important to understand how to work with POCOs after we finish our first version of the application.

Calculating a cross-partition aggregate with an asynchronous query

The following lines declare the code for the DoesCompetitionWithTitleExist asynchronous static method, which builds a query to count the number of competitions with the received title. In order to compute this aggregate, we must run a cross-partition query because the title for the competition can be at any location; that is, at any ZIP code. Cross-partition queries only support aggregates that use the VALUE keyword as a prefix. We learned about this keyword in the previous chapter.

As happened in other samples, there are other ways of achieving the same results. In this case, we use a similar pattern to the one we introduced in the GetCompetitionByTitle asynchronous static method. Add the following lines to the existing code of the Program.cs file. The code file for the sample is included in the learning_cosmos_db_05_01 folder in the dot_net_core_2_samples/SampleApp1/SampleApp1/Program.cs file:

private static async Task<bool> DoesCompetitionWithTitleExist(string competitionTitle)
{
    bool exists = false;
    // Retrieve the number of documents with a specific title
    // Very important: Cross partition queries only support 'VALUE<AggreateFunc>' for aggregates
    var collectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, collectionId);
    var documentCountQuery = client.CreateDocumentQuery(collectionUri,
        $"SELECT VALUE COUNT(1) FROM Competitions c WHERE c.title = '{competitionTitle}'",
         new FeedOptions()
        {
            EnableCrossPartitionQuery = true,
            MaxItemCount = 1,
        })
        .AsDocumentQuery();
    while (documentCountQuery.HasMoreResults)
    {
        var documentCountQueryResult = await documentCountQuery.ExecuteNextAsync();
        exists = (documentCountQueryResult.FirstOrDefault() == 1);     }
    return exists; 
}

The VALUE keyword makes the aggregate function return only the computed value without the key. The result of the query is a single value. Hence, we don't need to use foreach and we can use the FirstOrDefault method to retrieve the count value from the IEnumerable<dynamic> object provided by the FeedResponse<dynamic> object returned by the documentCountQuery.ExecuteNextAsync asynchronous method, which enumerates the results of the only page of the execution of the query.

Reading and updating an existing document with a dynamic object

Now we will write a method that updates the document that represents the second scheduled competition. Specifically, the method changes the values for the dateTime and numberOfRegisteredCompetitors keys.

At the time I was writing this book, the only way to update the value of any key in a document stored in a Cosmos DB collection was to replace the document with a new one. Since the first version of Cosmos DB and the SQL API, this is the only way to update the values for keys in a document. In this case, we just need to update the values for two keys, but we will have to replace the entire document.

The following lines declare the code for the UpdateScheduledCompetition asynchronous static method, which receives the competition ID, its location ZIP code, the new date and time, and the new number of registered competitors in the competitionId, competitionLocationZipCode, newDataTime, and newNumberOfRegisteredCompetitors arguments. The method retrieves the document whose ID matches the competitionId value received as an argument, casts the retrieved document as a dynamic object to update the values for the explained keys, and uses this dynamic object to replace the existing document. Add the following lines to the existing code of the Program.cs file. The code file for the sample is included in the learning_cosmos_db_04_01 folder in the dot_net_core_2_samples/SampleApp1/SampleApp1/Program.cs file:

private static async Task<Document> UpdateScheduledCompetition(string
competitionId,
    string competitionLocationZipCode,
    DateTime newDateTime,
    int newNumberOfRegisteredCompetitors)
{
    // Retrieve a document related to a competition that is scheduled
    // and update its date and its number of registered competitors
    // The read operation requires the partition key
    var documentToUpdateUri = UriFactory.CreateDocumentUri(databaseId, collectionId, competitionId);
    var readDocumentResponse = await client.ReadDocumentAsync(documentToUpdateUri, new RequestOptions()
    {
        PartitionKey = new PartitionKey(competitionLocationZipCode)
    });
    ((dynamic)readDocumentResponse.Resource).dateTime = newDateTime;
    ((dynamic)readDocumentResponse.Resource).numberOfRegisteredCompetitors = newNumberOfRegisteredCompetitors;
    ResourceResponse<Document> updatedDocumentResponse = await client.ReplaceDocumentAsync(
         documentToUpdateUri,
         readDocumentResponse.Resource);
    if (updatedDocumentResponse.StatusCode == System.Net.HttpStatusCode.OK)
    {
        Console.WriteLine($"The competition with id {competitionId} has been updated.");
     }
    return updatedDocumentResponse.Resource; }

The first line calls the UriFactory.CreateDocumentUri method with databaseId, collectionId, and competitionId (the document ID) as their arguments and saves the result in the documentToUpdateUri variable. This method will return a Uri instance with the URI for the document, generated with the combination of the database ID, the collection id, and the document ID received as arguments. The code will use this URI to easily address the document resource we want to retrieve.

The next line calls the client.ReadDocumentAsync asynchronous method with documentToUpdateUri and a new RequestOptions instance as the arguments. The value for the PartitionKey property of the RequestOptions instance is initialized to a new PartitionKey instance with the received competitionLocationZipCode received as an argument. This way, we specify the URI and the partition key to enable us to retrieve the document in the simplest and cheapest read operation when working with a partitioned document collection.

The client.ReadDocumentAsync method returns a ResourceResponse<Document> instance, which the code saves in the readDocumentResponse variable. The retrieved document resource is available in the readDocumentResponse.Resource property of this instance. In this case, the Resource property is of the previously explained Document type.

The next two lines cast the Document instance in the readDocumentResponse.Resource property to dynamic to set the new value for the dateTime and numberOfRegisteredCompetitors properties. It is necessary to cast to a dynamic object because we aren't using POCOs to represent the documents.

The next line calls the client.ReplaceDocumentAsync method with the following arguments:

  • documentToUpdateUri: The Uri instance for the document that will be replaced
  • readDocumentResponse.Resource: The document with the new values for the dateTime and numberOfRegisteredCompetitors properties

The use of dynamic objects might cause issues the appropriate type returned by the client.ReplaceDocumentAsync asynchronous method. Hence, in this case, the code specifies the type for the updatedDocumentResponse variable to which we assign the results of the asynchronous call.

The call to the client.ReplaceDocumentAsync method returns a ResourceResponse<Document> instance, which the code saves in the updatedDocumentResponse variable. The created document resource is available in the updatedDocumentResponse.Resource property of this instance. In this case, the Resource property is of the previously explained Document type.

The code checks the value of the HTTP status code available in the collectionResponse.StatusCode property to determine whether the document has been updated and displays a message if this property is equal to the HTTP 200 OK status (System.Net.HttpStatusCode.OK).

Finally, the method returns the updatedDocumentResponse.Resource property with the Document instance that was updated.

Querying documents in multiple partitions

The following lines declare the code for the ListScheduledCompetitions asynchronous static method, which builds a query to retrieve the titles for all the scheduled competitions that have more than 200 registered competitors and shows them in the console output. In order to retrieve these titles, we must run a cross-partition query because the competitions with more than 200 registered competitors can be at any location; that is, at any ZIP code.

As happened in other samples, there are other ways of achieving the same results. In this case, we use a similar pattern to the one we introduced in the GetCompetitionByTitle asynchronous static method. Add the following lines to the existing code of the Program.cs file. The code file for the sample is included in the

learning_cosmos_db_04_01 folder in the dot_net_core_2_samples/SampleApp1/SampleApp1/Program.cs file:

private static async Task ListScheduledCompetitions()
{
    // Retrieve the titles for all the scheduled competitions that have more than 200 registered competitors
    var collectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, collectionId);
    var selectTitleQuery = client.CreateDocumentQuery(collectionUri,
        $"SELECT VALUE c.title FROM Competitions c WHERE c.numberOfRegisteredCompetitors > 200 AND c.status = 'Scheduled'",
        new FeedOptions()
        {
            EnableCrossPartitionQuery = true,
            MaxItemCount = 100,
        })
        .AsDocumentQuery();
    while (selectTitleQuery.HasMoreResults)
    {
        var selectTitleQueryResult = await selectTitleQuery.ExecuteNextAsync();
        foreach (var title in selectTitleQueryResult)
        {
            Console.WriteLine(title);
        }
    }
}

In this case, the FeedOptions instance specifies that we want a MaxItemCount of 100 documents per page—that is, per call to the ExecuteNextAsync method—to iterate through the resultset page. We don't filter any specific zipCode, and therefore, we set the EnableCrossPartitionQuery to true to enable a cross-partition query.

The use of the VALUE keyword in the query makes it possible to retrieve the values without a key, and we can easily write a foreach block that writes a line in the console for each retrieved title.

Calling asynchronous methods that create and query dynamic documents

Now we will write the code for the CreateAndQueryDynamicDocumentsAsync asynchronous static method, which calls the previously created and explained asynchronous static methods. Add the following lines to the existing code of the Program.cs file. The code file for the sample is included in the learning_cosmos_db_04_01 folder in the dot_net_core_2_samples/SampleApp1/SampleApp1/Program.cs file:

        private static async Task CreateAndQueryDynamicDocumentsAsync()
        {
            var database = await RetrieveOrCreateDatabaseAsync();
            Console.WriteLine(
                $"The database {databaseId} is available for operations with the following AltLink: {database.AltLink}");
            var collection = await CreateCollectionIfNotExistsAsync();
            Console.WriteLine(
                $"The collection {collectionId} is available for operations with the following AltLink: {collection.AltLink}");
            string competition1Id = "1";
            string competition1Title = "Crowns for Gamers - Portland 2018";
            string competition1ZipCode = "90210";
            var competition1 = await GetCompetitionByTitle(competition1Title);
             if (competition1 == null)
            {
                competition1 = await InsertCompetition1(competition1Id, competition1Title, competition1ZipCode);
             }
            string competition2Title = "Defenders of the crown - San Diego 2018";
            bool isCompetition2Inserted = await DoesCompetitionWithTitleExist(competition2Title);
            string competition2Id = "2";
            string competition2LocationZipCode = "92075";
            if (isCompetition2Inserted)
            {
                Console.WriteLine(
                     $"The document with the following title exists: {competition2Title}");
            }
             else
            {
                var competition2 = await InsertCompetition2(competition2Id, competition2Title, competition2LocationZipCode);
             }
            var updatedCompetition2 = await UpdateScheduledCompetition(competition2Id,
                 competition2LocationZipCode,
                 DateTime.UtcNow.AddDays(60),
                250);
            await ListScheduledCompetitions();
        }
    }
}

The new method that is called by the Main method performs the following actions:

  1. Calls the RetrieveOrCreateDatabaseAsync method to create or retrieve the Cosmos DB document database specified in the appropriate key in the configuration.json file.
  2. Calls the CreateCollectionIfNotExistsAsync method to create or retrieve the Cosmos DB document collection specified in the appropriate key in the configuration.json file.
  3. Calls the GetCompetitionByTitle method to check whether a competition with a title that matches Crowns for Gamers - Portland 2018 exists. If the competition isn't found, the code calls the InsertCompetition1 method to insert the document that represents the first competition.
  4. Calls the DoesCompetitionWithTitleExist method to check whether a competition with a title that matches Defenders of the crown - San Diego 2018 exists. If the competition isn't found, the code calls the InsertCompetition2 method to insert the document that represents the second competition.
  5. Calls the UpdateScheduledCompetition method to update the date and time and the registered number of competitions for the second competition.
  6. Calls the ListScheduledCompetitions method to list the titles for all the scheduled competitions that have more than 200 registered competitors.

Now run the application for the first time and you will see the following messages in the console output:

The database Competition has been retrieved.
The database Competition is available for operations with the following
AltLink: dbs/Competition
The collection Competitions1 has been created.
The collection Competitions1 is available for operations with the following
AltLink: dbs/Competition/colls/Competitions1
The competition with the title Crowns for Gamers - Portland 2018 has been created. The competition with the title Defenders of the crown - San Diego 2018 has been created.
The competition with id 2 has been updated.
Defenders of the crown - San Diego 2018 Press any key to exit the console application.

Use your favorite tool to check the documents in the Cosmos DB database and collection that you have configured in the configuration.json file that the application uses. Make sure you refresh the appropriate screen in the selected tool. You will see two documents that belong to different partitions based on the value of the location.zipCode key. For example, the following screenshot shows the inserted and updated document whose id is equal to 2 and its location.zipCode is equal to 92075 in Microsoft Azure Storage Explorer:

Now run the application for the second time and you will see the following messages similar to the following in the console output:

The database Competition has been retrieved.
The database Competition is available for operations with the following AltLink: dbs/Competition The collection Competitions1 already exists. The collection Competitions1 is available for operations with the following AltLink: dbs/Competition/colls/Competitions1 The document with the following title exists: Crowns for Gamers – Portland 2018
{"id":"1","title":"Crowns for Gamers – Portland 2018","location":{"zipCode":"90210","state":"CA"},"platforms":["PS4","XBox" ,"Switch"],"games":["Fortnite","NBA Live 19"],"numberOfRegisteredCompetitors":80,"numberOfCompetitors":60,"numberOfV iewers":300,"status":"Finished","dateTime":"2018-07-23T04:00:52.2062076Z"," winners":[{"player":{"nickName":"EnzoTheGreatest","country":"Italy","city": "Rome"},"position":1,"score":7500,"prize":1500},{"player":{"nickName":"NicoInGamerLand","country":"Argentina","city":"Buenos
Aires"},"position":2,"score":6500,"prize":750},{"player":{"nickName":"KiwiBoy","country":"New Zealand","city":"Auckland"},"position":3,"score":3500,"prize":250}],"_rid":
"prUNALhv7LUfAAAAAAAAAA==","_self":"dbs/prUNAA==/colls/prUNALhv7LU=/docs/prUNALhv7LUfAAAAAAAAAA==/","_etag":"\"2700c717-0000-0000-0000-5b973df30000\"","_attachments":"attachments/","_ts":1536638451}
The document with the following title exists: Defenders of the crown – San Diego 2018 The competition with id 2 has been updated.
Defenders of the crown - San Diego 2018 Press any key to exit the console application.

Test your knowledge

Let's see whether you can answer the following questions correctly:

  1. Which of the following classes represents a document collection:
    1. Microsoft.Azure.Documents.Collection
    2. Microsoft.Azure.Documents.DocumentCollection
    3. Microsoft.Azure.Documents.Client.Collection
  2. Which of the following classes represents a JSON document that belongs to a document collection:
    1. Microsoft.Azure.Documents.Document
    2. Microsoft.Azure.Documents.Collection.Document
    3. Microsoft.Azure.Documents.Client.Document
  3. In the Microsoft.Azure.Documents.Resource class, which property is mapped to the JSON key ID:
    1. ResourceId
    2. Id
    3. SelfLink
  4. The responses from create, read, update and delete operations on any Cosmos DB resource return the resource response wrapped in an instance of which of the following classes:
    1. Resource
    2. Response
    3. ResourceResponse
  5. Which of the following classes provides static methods to create URI instances for databases, document collections, documents and other resources:
    1. UriFactory
    2. UriBuilder
    3. Uri

Summary

In this chapter, we worked with the main classes of the Cosmos DB SDK for .NET Core and we built our first .NET Core 2 application that interacts with Cosmos DB. We configured the Cosmos DB client and we wrote code to create or retrieve a document database, query and create document collections, and retrieve documents with asynchronous queries.

We wrote code that used dynamic objects to insert documents that represented competitions. We read and updated existing documents with dynamic objects and we calculated cross-partition aggregates.

Now that we have a very clear understanding of the basics of the .NET Core SDK with dynamic objects to perform create, read, and update operations with Cosmos DB, we will work with POCOs and LINQ queries, which are the topics we are going to discuss in the next chapter.