教程:使用适用于 NoSQL 的 Azure Cosmos DB 开发 .NET 控制台应用程序

适用范围: NoSQL

使用 Azure SDK for .NET,你可以通过异步单个操作事务批处理的方式向 API for NoSQL 容器添加数据。 本教程逐步讲解如何创建一个可将多个项添加到容器的新 .NET 控制台应用程序。

在本教程中,你将了解如何执行以下操作:

  • 使用 API for NoSQL 创建数据库
  • 创建 .NET 控制台应用程序并添加用于 .NET 的 Azure SDK
  • 将单个项添加到 API for NoSQL 容器
  • 从 API for NoSQL 容器中有效检索项
  • 为 API for NoSQL 容器创建包含批处理更改的事务

先决条件

创建 API for NoSQL 资源

首先,在现有的 API for NoSQL 帐户中创建一个空数据库。 稍后你将使用用于 .NET 的 Azure SDK 创建容器。

  1. Azure 门户中导航到现有的 API for NoSQL 帐户。

  2. 在资源菜单中选择“密钥”。

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

  3. 在“密钥”页上,找到并记下“URI”和“主密钥”字段的值。 这些值将在整篇教程中使用。

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

  4. 在资源菜单中,选择“数据资源管理器”。

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

  5. 在“数据资源管理器”页上,选择命令栏中的“新建数据库”选项。

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

  6. 在“新建数据库”对话框中,使用以下设置创建新容器:

    数据库 ID cosmicworks
    数据库吞吐量类型 手动
    数据库吞吐量 400

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

  7. 选择“确定”以创建数据库。

创建 .NET 控制台应用程序

现在,你将创建一个新的 .NET 控制台应用程序,并从 NuGet 使用 Microsoft.Azure.Cosmos 库导入用于 .NET 的 Azure SDK。

  1. 在空目录中打开终端。

  2. 使用 console 内置模板创建新的控制台应用程序

    dotnet new console --langVersion preview
    
  3. 从 NuGet 添加 3.31.1-preview 版本的 Microsoft.Azure.Cosmos 包。

    dotnet add package Microsoft.Azure.Cosmos --version 3.31.1-preview
    
  4. 另外,从 NuGet 添加 pre-release 版本的 System.CommandLine 包。

    dotnet add package System.CommandLine --prerelease
    
  5. 另外,从 NuGet 添加 Humanizer 包。

    dotnet add package Humanizer
    
  6. 生成控制台应用程序项目。

    dotnet build
    
  7. 使用当前项目文件夹作为工作区打开 Visual Studio Code。

    提示

    可以在终端中运行 code .,以打开 Visual Studio Code 并自动打开作为当前工作区的工作目录。

  8. 导航到 Program.cs 文件并将其打开。 删除该文件中的所有现有代码。

  9. 将以下代码添加到该文件,以使用 System.CommandLine 库来分析命令行中通过 --first--last 选项传入的两个字符串。

    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);
    

    注意

    对于本教程,你是否了解命令行分析程序的工作原理并不十分重要。 运行应用程序时,可为分析程序指定四个选项。 其中三个选项是必需的,因为它们用于构造 ID 和分区键字段。

  10. 由于你尚未定义静态 CosmosHandler.ManageCustomerAsync 方法,因此项目此时不会生成。

  11. 保存 Program.cs 文件。

使用 SDK 将项添加到容器

接下来,使用单独的操作将项添加到 API for NoSQL 容器中。 在本部分,你将定义 CosmosHandler.ManageCustomerAsync 方法。

  1. 创建新的 CosmosHandler.cs 文件。

  2. 在 CosmosHandler.cs 文件中,为 HumanizerMicrosoft.Azure.Cosmos 命名空间添加新的 using 指令。

    using Humanizer;
    using Microsoft.Azure.Cosmos;
    
  3. 创建名为 CosmosHandler 的新静态类。

    public static class CosmosHandler
    { }
    
  4. 创建静态 ManageCustomerAsync 方法的简短实现以输出命令行输入,目的只是为了验证此应用是否正常工作。

    public static async Task ManageCustomerAsync(string name, string email, string state, string country)
    {
        await Console.Out.WriteLineAsync($"Hello {name} of {state}, {country}!");
    }
    
  5. 保存 CosmosHandler.cs 文件。

  6. 回到终端并运行应用程序。

    dotnet run -- --name 'Mica Pereira' --state 'Washington' --country 'United States'
    
  7. 命令的输出应是一段有趣的问候。

    Hello Mica Pereira of Washington, United States!
    
  8. 返回到 CosmosHandler.cs 文件。

  9. 在静态 CosmosHandler 类中,添加一个名为 _client、类型为 CosmosClient 的新 private static readonly 成员。

    private static readonly CosmosClient _client;
    
  10. CosmosHandler 类创建新的静态构造函数。

    static CosmosHandler()
    { }
    
  11. 在该构造函数中,创建 CosmosClient 类的新实例并传入两个字符串参数,其中包含前面你在实验中记下的“URI”和“主密钥”值。 将此新实例存储在 _client 成员中。

    static CosmosHandler()
    {
        _client = new CosmosClient(
            accountEndpoint: "<uri>", 
            authKeyOrResourceToken: "<primary-key>"
        );
    }
    
  12. 回到静态 CosmosHandler 类,创建一个名为 GetContainerAsync 的新异步方法,该方法返回 Container

    private static async Task<Container> GetContainer()
    { }
    
  13. 对于后续步骤,请在 GetContainerAsync 方法中添加此代码。

    1. 获取 cosmicworks 数据库并将其存储在名为 database 的变量中。

      Database database = _client.GetDatabase("cosmicworks");
      
    2. 在分层分区键路径列表中创建 string 值的新泛型 List<>,并将其存储在名为 keyPaths 的变量中。

      List<string> keyPaths = new()
      {
          "/address/country",
          "/address/state"
      };
      
    3. 使用容器名称 (customers) 和分区键路径列表创建新的 ContainerProperties 变量。

      ContainerProperties properties = new(
          id: "customers",
          partitionKeyPaths: keyPaths
      );
      
    4. 使用 CreateContainerIfNotExistsAsync 方法提供容器属性并检索容器。 如果数据库中尚不存在该容器,则此方法将根据名称以异步方式创建该容器。 返回结果,即 GetContainerAsync 方法的输出。

      return await database.CreateContainerIfNotExistsAsync(
          containerProperties: properties
      );
      
  14. 删除 ManageCustomerAsync 方法中的所有代码。

  15. 对于后续步骤,请在 ManageCustomerAsync 方法中添加此代码。

    1. 以异步方式调用 GetContainerAsync 方法并将结果存储在名为 container 的变量中。

      Container container = await GetContainerAsync();
      
    2. 创建一个名为 id 的新变量,该变量使用 Humanizer 中的 Kebaberize 方法来转换 name 方法参数。

      string id = name.Kebaberize();
      

      注意

      Kebaberize 方法会将所有空格替换为连字符,并将文本转换为小写。

    3. 使用 namestatecountry 方法参数以及 id 变量创建一个新的匿名类型项。 将该项存储为名为 customer 的变量。

      var customer = new {
          id = id,
          name = name,
          address = new {
              state = state,
              country = country
          }
      };
      
    4. 使用容器的异步 CreateItemAsync 方法在容器中创建一个新项,并将 HTTP 响应元数据分配到名为 response 的变量。

      var response = await container.CreateItemAsync(customer);
      
    5. response 变量的 StatusCodeRequestCharge 属性值写入控制台。 另外,写入 id 变量的值。

      Console.WriteLine($"[{response.StatusCode}]\t{id}\t{response.RequestCharge} RUs");
      
  16. 保存 CosmosHandler.cs 文件。

  17. 回到终端并再次运行应用程序。

    dotnet run -- --name 'Mica Pereira' --state 'Washington' --country 'United States'
    
  18. 命令的输出应包含操作的状态和请求费用。

    [Created]       mica-pereira    7.05 RUs
    

    注意

    你的请求费用可能与此不同。

  19. 再次运行应用程序。

    dotnet run -- --name 'Mica Pereira' --state 'Washington' --country 'United States'
    
  20. 这一次,程序应会崩溃。 如果你滚动浏览错误消息的话,将会看到崩溃原因是项的唯一标识符有冲突。

    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."
        ]
    );
    

使用 SDK 检索项

在容器中创建第一个项后,可以使用相同的 SDK 来检索该项。 在此处,你将查询该项并对其进行点读取,以比较请求单位 (RU) 消耗量的差异。

  1. 返回到 CosmosHandler.cs 文件或将其打开。

  2. ManageCustomerAsync 方法中删除除前两行之外的所有代码行。

    public static async Task ManageCustomerAsync(string name, string email, string state, string country)
    {
        Container container = await GetContainerAsync();
    
        string id = name.Kebaberize();
    }
    
  3. 对于后续步骤,请在 ManageCustomerAsync 方法中添加此代码。

    1. 使用容器的异步 CreateItemAsync 方法在容器中创建一个新项,并将 HTTP 响应元数据分配到名为 response 的变量。

      var response = await container.CreateItemAsync(customer);
      
    2. 使用 SQL 查询创建名为 sql 的新字符串,以检索筛选器 (@id) 匹配的项。

      string sql = """
      SELECT
          *
      FROM customers c
      WHERE c.id = @id
      """;
      
    3. 创建名为 query 的新 QueryDefinition 变量,并传入 sql 字符串作为唯一一个查询参数。 另外,使用 WithParameter fluid 方法将变量 id 的值应用于 @id 参数。

      var query = new QueryDefinition(
          query: sql
      )
          .WithParameter("@id", id);
      
    4. 使用 GetItemQueryIterator<> 泛型方法和 query 变量创建从 Azure Cosmos DB 获取数据的迭代器。 将该迭代器存储在名为 feed 的变量中。 将整个表达式包装在 using 语句中,以便稍后释放该迭代器。

      using var feed = container.GetItemQueryIterator<dynamic>(
          queryDefinition: query
      );
      
    5. 以异步方式调用 feed 变量的 ReadNextAsync 方法并将结果存储在名为 response 的变量中。

      var response = await feed.ReadNextAsync();
      
    6. response 变量的 StatusCodeRequestCharge 属性值写入控制台。 另外,写入 id 变量的值。

      Console.WriteLine($"[{response.StatusCode}]\t{id}\t{response.RequestCharge} RUs");
      
  4. 保存 CosmosHandler.cs 文件。

  5. 回到终端,运行应用程序以使用 SQL 查询读取单个项。

    dotnet run -- --name 'Mica Pereira'
    
  6. 命令的输出应指示查询需要多个 RU。

    [OK]    mica-pereira    2.82 RUs
    
  7. 回到 CosmosHandler.cs 文件,再次从 ManageCustomerAsync 方法中删除除前两行之外的所有代码行。

    public static async Task ManageCustomerAsync(string name, string email, string state, string country)
    {
        Container container = await GetContainerAsync();
    
        string id = name.Kebaberize();
    }
    
  8. 对于后续步骤,请在 ManageCustomerAsync 方法中添加此代码。

    1. 通过将 statecountry 参数添加为多部分分区键值来创建 PartitionKeyBuilder 的新实例。

      var partitionKey = new PartitionKeyBuilder()
          .Add(country)
          .Add(state)
          .Build();
      
    2. 使用容器的 ReadItemAsync<> 方法通过 idpartitionKey 变量对容器中的项进行点读取。 将结果保存在名为 response 的变量中。

      var response = await container.ReadItemAsync<dynamic>(
          id: id, 
          partitionKey: partitionKey
      );
      
    3. response 变量的 StatusCodeRequestCharge 属性值写入控制台。 另外,写入 id 变量的值。

      Console.WriteLine($"[{response.StatusCode}]\t{id}\t{response.RequestCharge} RU");
      
  9. 再次保存 CosmosHandler.cs 文件。

  10. 回到终端,再次运行应用程序以便对单个项进行点读取。

    dotnet run -- --name 'Mica Pereira' --state 'Washington' --country 'United States'
    
  11. 命令的输出应指示查询需要单个 RU。

    [OK]    mica-pereira    1 RUs
    

使用 SDK 创建事务

最后,你将获取创建的项,读取该项,并使用用于 .NET 的 Azure SDK 创建另一个相关项作为单个事务的一部分。

  1. 返回到 CosmosHandler.cs 文件或将其打开。

  2. ManageCustomerAsync 方法中删除以下代码行。

    var response = await container.ReadItemAsync<dynamic>(
        id: id, 
        partitionKey: partitionKey
    );
    
    Console.WriteLine($"[{response.StatusCode}]\t{id}\t{response.RequestCharge} RUs");
    
  3. 对于后续步骤,请在 ManageCustomerAsync 方法中添加此新代码。

    1. 使用 namestatecountry 方法参数以及 id 变量创建一个新的匿名类型项。 将该项存储为名为 customerCart 的变量。 此项代表客户的实时购物车,它当前是空的。

      var customerCart = new {
          id = $"{Guid.NewGuid()}",
          customerId = id,
          items = new string[] {},
          address = new {
              state = state,
              country = country
          }
      };
      
    2. 使用 namestatecountry 方法参数以及 id 变量创建另一个新的匿名类型项。 将该项存储为名为 customerCart 的变量。 此项代表客户的发货和联系信息。

      var customerContactInfo = new {
          id = $"{id}-contact",
          customerId = id,
          email = email,
          location = $"{state}, {country}",
          address = new {
              state = state,
              country = country
          }
      };
      
    3. 使用容器的 CreateTransactionalBatch 方法并传入 partitionKey 变量来创建一个新的批处理。 将该批处理存储在名为 batch 的变量中。 使用 fluent 方法执行以下操作:

      方法 参数
      ReadItem id 字符串变量
      CreateItem customerCart 匿名类型变量
      CreateItem customerContactInfo 匿名类型变量
      var batch = container.CreateTransactionalBatch(partitionKey)
          .ReadItem(id)
          .CreateItem(customerCart)
          .CreateItem(customerContactInfo);
      
    4. 使用批处理的 ExecuteAsync 方法启动事务。 将结果保存在名为 response 的变量中。

      using var response = await batch.ExecuteAsync();
      
    5. response 变量的 StatusCodeRequestCharge 属性值写入控制台。 另外,写入 id 变量的值。

      Console.WriteLine($"[{response.StatusCode}]\t{response.RequestCharge} RUs");
      
  4. 再次保存 CosmosHandler.cs 文件。

  5. 回到终端,再次运行应用程序以便对单个项进行点读取。

    dotnet run -- --name 'Mica Pereira' --state 'Washington' --country 'United States'
    
  6. 命令的输出应显示整个事务使用的请求单位数。

    [OK]    16.05 RUs
    

    注意

    你的请求费用可能与此不同。

验证数据资源管理器中的最终数据

最后,使用 Azure 门户中的数据资源管理器来查看你在本教程中创建的数据和容器。

  1. Azure 门户中导航到现有的 API for NoSQL 帐户。

  2. 在资源菜单中,选择“数据资源管理器”。

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

  3. 在“数据资源管理器”页上展开 cosmicworks 数据库,然后选择 customers 容器。

    Screenshot of the selected container node within the database node.

  4. 在命令栏中,选择“新建 SQL 查询”。

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

  5. 在查询编辑器中找到以下 SQL 查询字符串。

    SELECT * FROM c
    
  6. 选择“执行查询”以运行该查询并观察结果。

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

  7. 结果应包含一个 JSON 数组,其中包含本教程中创建的三个项。 可以看到,所有项具有相同的分层分区键值,但 ID 字段值是唯一的。 为简洁起见,包含的示例输出已截断。

    [
      {
        "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"
        },
        ...
      }
    ]
    

清理资源

今后不再需要本教程中使用的数据库时,请将其删除。 为此,请导航到帐户页,选择“数据资源管理器”,选择 cosmicworks 数据库,然后选择“删除”。

后续步骤

使用 Azure Cosmos DB 创建第一个 .NET 控制台应用程序后,接下来请尝试学习下一篇教程,了解如何将现有 Web 应用程序更新为使用 Azure Cosmos DB 数据。