Tutorial: Develop a .NET console application with Azure Cosmos DB for NoSQL

APPLIES TO: NoSQL

The Azure SDK for .NET allows you to add data to an API for NoSQL container either asynchronous individual operations or a transactional batch. This tutorial walks through the process of create a new .NET console application that adds multiple items to a container.

In this tutorial, you learn how to:

  • Create a database using API for NoSQL
  • Create a .NET console application and add the Azure SDK for .NET
  • Add individual items into an API for NoSQL container
  • Retrieve items efficient from an API for NoSQL container
  • Create a transaction with batch changes for the API for NoSQL container

Prerequisites

Create API for NoSQL resources

First, create an empty database in the existing API for NoSQL account. You create a container using the Azure SDK for .NET later.

  1. Navigate to your existing API for NoSQL account in the Azure portal.

  2. In the resource menu, select Keys.

    Screenshot of an API for NoSQL account page. The Keys option is highlighted in the resource menu.

  3. On the Keys page, observe and record the value of the URI and PRIMARY KEY fields. These values are used throughout the tutorial.

    Screenshot of the Keys page with the URI and Primary Key fields highlighted.

  4. In the resource menu, select Data Explorer.

    Screenshot of the Data Explorer option highlighted in the resource menu.

  5. On the Data Explorer page, select the New Database option in the command bar.

    Screenshot of the New Database option in the Data Explorer command bar.

  6. In the New Database dialog, create a new container with the following settings:

    Value
    Database id cosmicworks
    Database throughput type Manual
    Database throughput amount 400

    Screenshot of the New Database dialog in the Data Explorer with various values in each field.

  7. Select OK to create the database.

Create .NET console application

Now, you create a new .NET console application and import the Azure SDK for .NET by using the Microsoft.Azure.Cosmos library from NuGet.

  1. Open a terminal in an empty directory.

  2. Create a new console application using the console built-in template

    dotnet new console --langVersion preview
    
  3. Add the 3.31.1-preview version of the Microsoft.Azure.Cosmos package from NuGet.

    dotnet add package Microsoft.Azure.Cosmos --version 3.31.1-preview
    
  4. Also, add the pre-release version of the System.CommandLine package from NuGet.

    dotnet add package System.CommandLine --prerelease
    
  5. Also, add the Humanizer package from NuGet.

    dotnet add package Humanizer
    
  6. Build the console application project.

    dotnet build
    
  7. Open Visual Studio Code using the current project folder as the workspace.

    Tip

    You can run code . in the terminal to open Visual Studio Code and automatically open the working directory as the current workspace.

  8. Navigate to and open the Program.cs file. Delete all of the existing code in the file.

  9. Add this code to the file to use the System.CommandLine library to parse the command line for two strings passed in through the --first and --last options.

    using System.CommandLine;
    
    var command = new RootCommand();
    
    var nameOption = new Option<string>("--name") { IsRequired = true };
    var emailOption = new Option<string>("--email");
    var stateOption = new Option<string>("--state") { IsRequired = true };
    var countryOption = new Option<string>("--country") { IsRequired = true };
    
    command.AddOption(nameOption);
    command.AddOption(emailOption);
    command.AddOption(stateOption);
    command.AddOption(countryOption);
    
    command.SetHandler(
        handle: CosmosHandler.ManageCustomerAsync, 
        nameOption, 
        emailOption,
        stateOption,
        countryOption
    );
    
    await command.InvokeAsync(args);
    

    Note

    For this tutorial, it's not entirely important that you understand how the command-line parser works. The parser has four options that can be specified when the application is running. Three of the options are required since they will be used to construct the ID and partition key fields.

  10. At this point, the project won't build since you haven't defined the static CosmosHandler.ManageCustomerAsync method yet.

  11. Save the Program.cs file.

Add items to a container using the SDK

Next, you use individual operations to add items into the API for NoSQL container. In this section, you define the CosmosHandler.ManageCustomerAsync method.

  1. Create a new CosmosHandler.cs file.

  2. In the CosmosHandler.cs file, add a new using directive for the Humanizer and Microsoft.Azure.Cosmos namespaces.

    using Humanizer;
    using Microsoft.Azure.Cosmos;
    
  3. Create a new static class named CosmosHandler.

    public static class CosmosHandler
    { }
    
  4. Just to validate this app works, create a short implementation of the static ManageCustomerAsync method to print the command-line input.

    public static async Task ManageCustomerAsync(string name, string email, string state, string country)
    {
        await Console.Out.WriteLineAsync($"Hello {name} of {state}, {country}!");
    }
    
  5. Save the CosmosHandler.cs file.

  6. Back in the terminal, run the application.

    dotnet run -- --name 'Mica Pereira' --state 'Washington' --country 'United States'
    
  7. The output of the command should be a fun greeting.

    Hello Mica Pereira of Washington, United States!
    
  8. Return to the CosmosHandler.cs file.

  9. Within the static CosmosHandler class, add a new private static readonly member of type CosmosClient named _client.

    private static readonly CosmosClient _client;
    
  10. Create a new static constructor for the CosmosHandler class.

    static CosmosHandler()
    { }
    
  11. Within the constructor, create a new instance of the CosmosClient class passing in two string parameters with the URI and PRIMARY KEY values you previously recorded in the lab. Store this new instance in the _client member.

    static CosmosHandler()
    {
        _client = new CosmosClient(
            accountEndpoint: "<uri>", 
            authKeyOrResourceToken: "<primary-key>"
        );
    }
    
  12. Back within the static CosmosHandler class, create a new asynchronous method named GetContainerAsync that returns an Container.

    private static async Task<Container> GetContainerAsync()
    { }
    
  13. For the next steps, add this code within the GetContainerAsync method.

    1. Get the cosmicworks database and store it in a variable named database.

      Database database = _client.GetDatabase("cosmicworks");
      
    2. Create a new generic List<> of string values within a list of hierarchical partition key paths and store it in a variable named keyPaths.

      List<string> keyPaths = new()
      {
          "/address/country",
          "/address/state"
      };
      
    3. Create a new ContainerProperties variable with the name of the container (customers) and the list of partition key paths.

      ContainerProperties properties = new(
          id: "customers",
          partitionKeyPaths: keyPaths
      );
      
    4. Use the CreateContainerIfNotExistsAsync method to supply the container properties and retrieve the container. This method will, per the name, asynchronously create the container if it doesn't already exist within the database. Return the result as the output of the GetContainerAsync method.

      return await database.CreateContainerIfNotExistsAsync(
          containerProperties: properties
      );
      
  14. Delete all of the code within the ManageCustomerAsync method.

  15. For the next steps, add this code within the ManageCustomerAsync method.

    1. Asynchronously call the GetContainerAsync method and store the result in a variable named container.

      Container container = await GetContainerAsync();
      
    2. Create a new variable named id that uses the Kebaberize method from Humanizer to transform the name method parameter.

      string id = name.Kebaberize();
      

      Note

      The Kebaberize method will replace all spaces with hyphens and conver the text to lowercase.

    3. Create a new anonymous typed item using the name, state, and country method parameters and the id variable. Store the item as a variable named customer.

      var customer = new {
          id = id,
          name = name,
          address = new {
              state = state,
              country = country
          }
      };
      
    4. Use the container's asynchronous CreateItemAsync method to create a new item in the container and assign the HTTP response metadata to a variable named response.

      var response = await container.CreateItemAsync(customer);
      
    5. Write the values of the response variable's StatusCode and RequestCharge properties to the console. Also write the value of the id variable.

      Console.WriteLine($"[{response.StatusCode}]\t{id}\t{response.RequestCharge} RUs");
      
  16. Save the CosmosHandler.cs file.

  17. Back in the terminal, run the application again.

    dotnet run -- --name 'Mica Pereira' --state 'Washington' --country 'United States'
    
  18. The output of the command should include a status and request charge for the operation.

    [Created]       mica-pereira    7.05 RUs
    

    Note

    Your request charge may vary.

  19. Run the application one more time.

    dotnet run -- --name 'Mica Pereira' --state 'Washington' --country 'United States'
    
  20. This time, the program should crash. If you scroll through the error message, you see the crash occurred because of a conflict in the unique identifier for the items.

    Unhandled exception: Microsoft.Azure.Cosmos.CosmosException : Response status code does not indicate success: Conflict (409);Reason: (
        Errors : [
          "Resource with specified id or name already exists."
        ]
    );
    

Retrieve an item using the SDK

Now that you've created your first item in the container, you can use the same SDK to retrieve the item. Here, you'll query and point read the item to compare the difference in request unit (RU) consumption.

  1. Return to or open the CosmosHandler.cs file.

  2. Delete all lines of code from the ManageCustomerAsync method except for the first two lines.

    public static async Task ManageCustomerAsync(string name, string email, string state, string country)
    {
        Container container = await GetContainerAsync();
    
        string id = name.Kebaberize();
    }
    
  3. For the next steps, add this code within the ManageCustomerAsync method.

    1. Use the container's asynchronous CreateItemAsync method to create a new item in the container and assign the HTTP response metadata to a variable named response.

      var response = await container.CreateItemAsync(customer);
      
    2. Create a new string named sql with a SQL query to retrieve items where a filter (@id) matches.

      string sql = @"
      SELECT
          *
      FROM customers c
      WHERE c.id = @id
      ";
      
    3. Create a new QueryDefinition variable named query passing in the sql string as the only query parameter. Also, use the WithParameter fluid method to apply the value of the variable id to the @id parameter.

      var query = new QueryDefinition(
          query: sql
      )
          .WithParameter("@id", id);
      
    4. Use the GetItemQueryIterator<> generic method and the query variable to create an iterator that gets data from Azure Cosmos DB. Store the iterator in a variable named feed. Wrap this entire expression in a using statement to dispose the iterator later.

      using var feed = container.GetItemQueryIterator<dynamic>(
          queryDefinition: query
      );
      
    5. Asynchronously call the ReadNextAsync method of the feed variable and store the result in a variable named response.

      var response = await feed.ReadNextAsync();
      
    6. Write the values of the response variable's StatusCode and RequestCharge properties to the console. Also write the value of the id variable.

      Console.WriteLine($"[{response.StatusCode}]\t{id}\t{response.RequestCharge} RUs");
      
  4. Save the CosmosHandler.cs file.

  5. Back in the terminal, run the application to read the single item using a SQL query.

    dotnet run -- --name 'Mica Pereira' --state 'Washington' --country 'United States'
    
  6. The output of the command should indicate that the query required multiple request units (RUs).

    [OK]    mica-pereira    2.82 RUs
    
  7. Back in the CosmosHandler.cs file, delete all lines of code from the ManageCustomerAsync method again except for the first two lines.

    public static async Task ManageCustomerAsync(string name, string email, string state, string country)
    {
        Container container = await GetContainerAsync();
    
        string id = name.Kebaberize();
    }
    
  8. For the next steps, add this code within the ManageCustomerAsync method.

    1. Create a new instance of PartitionKeyBuilder by adding the state and country parameters as a multi-part partition key value.

      var partitionKey = new PartitionKeyBuilder()
          .Add(country)
          .Add(state)
          .Build();
      
    2. Use the container's ReadItemAsync<> method to point read the item from the container using the id and partitionKey variables. Save the result in a variable named response.

      var response = await container.ReadItemAsync<dynamic>(
          id: id, 
          partitionKey: partitionKey
      );
      
    3. Write the values of the response variable's StatusCode and RequestCharge properties to the console. Also write the value of the id variable.

      Console.WriteLine($"[{response.StatusCode}]\t{id}\t{response.RequestCharge} RU");
      
  9. Save the CosmosHandler.cs file again.

  10. Back in the terminal, run the application one more time to point read the single item.

    dotnet run -- --name 'Mica Pereira' --state 'Washington' --country 'United States'
    
  11. The output of the command should indicate that the query required a single RU.

    [OK]    mica-pereira    1 RUs
    

Create a transaction using the SDK

Finally, you take the item you created, read that item, and create a different related item as part of a single transaction using the Azure SDK for .NET.

  1. Return to or open the CosmosHandler.cs file.

  2. Delete these lines of code from the ManageCustomerAsync method.

    var response = await container.ReadItemAsync<dynamic>(
        id: id, 
        partitionKey: partitionKey
    );
    
    Console.WriteLine($"[{response.StatusCode}]\t{id}\t{response.RequestCharge} RUs");
    
  3. For the next steps, add this new code within the ManageCustomerAsync method.

    1. Create a new anonymous typed item using the name, state, and country method parameters and the id variable. Store the item as a variable named customerCart. This item represents a real-time shopping cart for the customer that is currently empty.

      var customerCart = new {
          id = $"{Guid.NewGuid()}",
          customerId = id,
          items = new string[] {},
          address = new {
              state = state,
              country = country
          }
      };
      
    2. Create another new anonymous typed item using the name, state, and country method parameters and the id variable. Store the item as a variable named customerCart. This item represents shipping and contact information for the customer.

      var customerContactInfo = new {
          id = $"{id}-contact",
          customerId = id,
          email = email,
          location = $"{state}, {country}",
          address = new {
              state = state,
              country = country
          }
      };
      
    3. Create a new batch using the container's CreateTransactionalBatch method passing in the partitionKey variable. Store the batch in a variable named batch. Use fluent methods to perform the following actions:

      Method Parameter
      ReadItem id string variable
      CreateItem customerCart anonymous type variable
      CreateItem customerContactInfo anonymous type variable
      var batch = container.CreateTransactionalBatch(partitionKey)
          .ReadItem(id)
          .CreateItem(customerCart)
          .CreateItem(customerContactInfo);
      
    4. Use the batch's ExecuteAsync method to start the transaction. Save the result in a variable named response.

      using var response = await batch.ExecuteAsync();
      
    5. Write the values of the response variable's StatusCode and RequestCharge properties to the console. Also write the value of the id variable.

      Console.WriteLine($"[{response.StatusCode}]\t{response.RequestCharge} RUs");
      
  4. Save the CosmosHandler.cs file again.

  5. Back in the terminal, run the application one more time to point read the single item.

    dotnet run -- --name 'Mica Pereira' --state 'Washington' --country 'United States'
    
  6. The output of the command should show the request units used for the entire transaction.

    [OK]    16.05 RUs
    

    Note

    Your request charge may vary.

Validate the final data in the Data Explorer

To wrap up things, you use the Data Explorer in the Azure portal to view the data, and container you created in this tutorial.

  1. Navigate to your existing API for NoSQL account in the Azure portal.

  2. In the resource menu, select Data Explorer.

    Screenshot of the Data Explorer option highlighted in the resource menu.

  3. On the Data Explorer page, expand the cosmicworks database, and then select the customers container.

    Screenshot of the selected container node within the database node.

  4. In the command bar, select New SQL query.

    Screenshot of the New SQL Query option in the Data Explorer command bar.

  5. In the query editor, observe this SQL query string.

    SELECT * FROM c
    
  6. Select Execute Query to run the query and observe the results.

    Screenshot of the 'Execute Query' option in the Data Explorer command bar.

  7. The results should include a JSON array with three items created in this tutorial. Observe that all of the items have the same hierarchical partition key value, but unique ID fields. The example output included is truncated for brevity.

    [
      {
        "id": "mica-pereira",
        "name": "Mica Pereira",
        "address": {
          "state": "Washington",
          "country": "United States"
        },
        ...
      },
      {
        "id": "33d03318-6302-4559-b5c0-f3cc643b2f38",
        "customerId": "mica-pereira",
        "items": [],
        "address": {
          "state": "Washington",
          "country": "United States"
        },
        ...
      },
      {
        "id": "mica-pereira-contact",
        "customerId": "mica-pereira",
        "email": null,
        "location": "Washington, United States",
        "address": {
          "state": "Washington",
          "country": "United States"
        },
        ...
      }
    ]
    

Clean up resources

When no longer needed, delete the database used in this tutorial. To do so, navigate to the account page, select Data Explorer, select the cosmicworks database, and then select Delete.