教程:使用 Azure Cosmos DB for NoSQL 开发 ASP.NET Web 应用程序

适用范围: NoSQL

用于 .NET 的 Azure SDK 允许使用 C# 中的 LINQSQL 查询字符串查询 API for NoSQL 容器中的数据。 本教程逐步讲解更新现有 ASP.NET Web 应用程序的过程,该应用程序使用占位符从 API 查询数据。

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

  • 使用 API for NoSQL 创建并填充数据库和容器
  • 从模板创建 ASP.NET Web 应用程序
  • 使用用于 .NET 的 Azure SDK 从 API for NoSQL 容器查询数据

先决条件

创建 API for NoSQL 资源

首先,在现有的 API for NoSQL 帐户中创建数据库和容器。 然后,你将使用 cosmicworks dotnet 工具在此帐户中填充数据。

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

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

    NoSQL 帐户页的 API 屏幕截图。突出显示了资源菜单中的“密钥”选项。

  3. 在“密钥”页面上,观察并记录“主要连接字符串”*字段的值。 此值将在整个教程中使用。

    “密钥”页的屏幕截图,其中突出显示了“URI”、“主密钥”和“主连接字符串”字段。

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

    突出显示了资源菜单中“数据资源管理器”选项的屏幕截图。

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

    数据资源管理器命令栏中“新建容器”选项的屏幕截图。

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

    设置
    数据库 ID cosmicworks
    数据库吞吐量类型 手动
    数据库吞吐量 1000
    容器 ID products
    分区键 /category/name

    数据资源管理器中的“新建容器”对话框以及每个字段中的各种值的屏幕截图。

    重要

    在本教程中,我们会首先将数据库的共享吞吐量纵向扩展到 1,000 RU/s,以最大限度地提高数据迁移性能。 数据迁移完成后,我们将预配吞吐量缩减到 400 RU/秒。

  7. 选择“确定”创建数据库和容器。

  8. 打开终端以运行命令,从而使用数据填充容器。

    提示

    这里可以选择使用 Azure Cloud Shell。

  9. 从 NuGet 安装 cosmicworks dotnet 工具的 v2 版本

    dotnet tool install --global cosmicworks  --version 2.*
    
  10. 通过 cosmicworks 工具使用前面在本实验中记下的“URI”和“主密钥”值,在 API for NoSQL 帐户中填充示例产品数据。 记下的这些值分别用于 endpointkey 参数。

    cosmicworks \
        --number-of-products 1759 \
        --number-of-employees 0 \
        --disable-hierarchical-partition-keys \
        --connection-string <nosql-connection-string>
    
  11. 观察命令行工具的输出。 它应向容器添加 1759 项。 为简洁起见,包含的示例输出已截断。

    ── Parsing connection string ────────────────────────────────────────────────────────────────
    ╭─Connection string──────────────────────────────────────────────────────────────────────────╮
    │ AccountEndpoint=https://<account-name>.documents.azure.cn:443/;AccountKey=<account-key>;  │
    ╰────────────────────────────────────────────────────────────────────────────────────────────╯
    ── Populating data ──────────────────────────────────────────────────────────────────────────
    ╭─Products configuration─────────────────────────────────────────────────────────────────────╮
    │ Database   cosmicworks                                                                     │
    │ Container  products                                                                        │
    │ Count      1,759                                                                           │
    ╰────────────────────────────────────────────────────────────────────────────────────────────╯
    ...
    [SEED]  00000000-0000-0000-0000-000000005951 | Road-650 Black, 60 - Bikes
    [SEED]  00000000-0000-0000-0000-000000005950 | Mountain-100 Silver, 42 - Bikes
    [SEED]  00000000-0000-0000-0000-000000005949 | Men's Bib-Shorts, L - Clothing
    [SEED]  00000000-0000-0000-0000-000000005948 | ML Mountain Front Wheel - Components
    [SEED]  00000000-0000-0000-0000-000000005947 | Mountain-500 Silver, 42 - Bikes
    
  12. 返回到帐户的“数据资源管理器”页。

  13. 在“数据”部分展开 cosmicworks 数据库节点,然后选择“缩放”。

    数据库节点中“缩放”选项的屏幕截图。

  14. 将吞吐量从 1,000 减少到 400。

    已降低至 400 RU/秒的数据库吞吐量设置的屏幕截图。

  15. 在命令栏中,选择“保存”。

    数据资源管理器命令栏中“保存”选项的屏幕截图。

  16. 在“数据”部分,展开并选择“产品”容器节点。

    数据库节点中展开的容器节点的屏幕截图。

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

    数据资源管理器命令栏中“新建 SQL 查询”选项的屏幕截图。

  18. 在查询编辑器中添加以下 SQL 查询字符串。

    SELECT
      p.sku,
      p.price
    FROM products p
    WHERE p.price < 2000
    ORDER BY p.price DESC
    
  19. 选择“执行查询”以运行该查询并观察结果。

    数据资源管理器命令栏中“执行查询”选项的屏幕截图。

  20. 结果应该是容器中 price 值小于 2,000 的所有项的分页数组,这些项按照价格从高到低的顺序排序。 为简洁起见,此处只显示了一部分输出。

    [
      {
        "sku": "BK-R79Y-48",
        "price": 1700.99
      },
      ...
      {
        "sku": "FR-M94B-46",
        "price": 1349.6
      },
    ...
    
  21. 将查询编辑器的内容替换为以下查询,然后再次选择“执行查询”以观察结果。

    SELECT
        p.name,
        p.category.name AS category,
        p.category.subCategory.name AS subcategory,
        p.tags
    FROM products p
    JOIN tag IN p.tags
    WHERE STRINGEQUALS(tag, "yellow", true)
    
  22. 结果应该是一个更小的项数组,该数组经过筛选,仅包含至少有一个符合以下条件的标记的项:name 值为 Tag-32。 同样,为简洁起见,此处只显示了一部分输出。

    [
      ...
      {
        "name": "HL Touring Frame - Yellow, 60",
        "category": "Components",
        "subcategory": "Touring Frames",
        "tags": [
          "Components",
          "Touring Frames",
          "Yellow",
          "60"
        ]
      },
      ...
    ]
    

创建 ASP.NET Web 应用程序

现在,你将使用示例项目模板创建一个新的 ASP.NET Web 应用程序。 然后,你将探索源代码并运行示例以熟悉该应用程序,然后使用用于 .NET 的 Azure SDK 添加 Azure Cosmos DB 连接。

重要

本教程以透明方式从 NuGet 拉取包。 你可以使用 dotnet nuget list source 来验证包源。 如果你未使用 NuGet 作为包源,请使用 dotnet nuget add source 将站点安装为源。

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

  2. 从 NuGet 安装 cosmicworks.template.web 项目模板包。

    dotnet new install cosmicworks.template.web
    
  3. 使用新安装的 dotnet new cosmosdbnosql-webapp 模板创建一个新的 Web 应用程序项目。

    dotnet new cosmosdbnosql-webapp
    
  4. 生成并运行 Web 应用程序项目。

    dotnet run
    
  5. 观察 run 命令的输出。 输出应包含运行该应用程序的端口和 URL 的列表。

    ...
    info: Microsoft.Hosting.Lifetime[14]
          Now listening on: http://localhost:5000
    info: Microsoft.Hosting.Lifetime[14]
          Now listening on: https://localhost:5001
    info: Microsoft.Hosting.Lifetime[0]
          Application started. Press Ctrl+C to shut down.
    info: Microsoft.Hosting.Lifetime[0]
          Hosting environment: Production
    ...
    
  6. 打开新的浏览器并导航到正在运行的 Web 应用程序。 观察正在运行的应用程序的所有三个页面。

    使用占位符数据运行的示例 Web 应用程序的屏幕截图。

  7. 通过终止正在运行的进程来停止正在运行的应用程序。

    提示

    使用 Ctrl+C 命令停止正在运行的进程。或者,可以关闭再重新打开终端

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

    提示

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

  9. 导航到 Services/ICosmosService.cs 文件并将其打开。 观察 RetrieveActiveProductsAsyncRetrieveAllProductsAsync 默认方法实现。 这些方法将创建一个静态产品列表,以便在首次运行项目时使用。 此处提供了一个已截断的方法示例。

    public async Task<IEnumerable<Product>> RetrieveActiveProductsAsync()
    {
        await Task.Delay(1);
    
        return new List<Product>()
        {
            new Product(id: "baaa4d2d-5ebe-45fb-9a5c-d06876f408e0", category: new Category(name: "Components, Road Frames"), sku: "FR-R72R-60", name: """ML Road Frame - Red, 60""", description: """The product called "ML Road Frame - Red, 60".""", price: 594.83000000000004m),
            new Product(id: "bd43543e-024c-4cda-a852-e29202310214", category: new Category(name: "Components, Forks"), sku: "FK-5136", name: """ML Fork""", description: """The product called "ML Fork".""", price: 175.49000000000001m),
            ...
        };
    }
    
  10. 导航到 Services/CosmosService.cs 文件并将其打开。 观察 CosmosService 类的当前实现。 此类实现 ICosmosService 接口,但不重写任何方法。 在此上下文中,该类将使用默认接口实现,直到在接口中提供了该实现的重写。

    public class CosmosService : ICosmosService
    { }
    
  11. 最后,导航到并打开 Models/Product.cs 和 Models/Category.cs 文件。 观察每个文件中定义的记录类型。 这些类型将在本教程中的查询中使用。

    public record Product(
        string id,
        Category category,
        string sku,
        string name,
        string description,
        decimal price
    );
    
    public record Category(
        string name
    );
    

使用 .NET SDK 查询数据

接下来,将用于 .NET 的 Azure SDK 添加到此示例项目,然后使用库从 API for NoSQL 容器查询数据。

  1. 返回终端,从 NuGet 添加 Microsoft.Azure.Cosmos 包。

    dotnet add package Microsoft.Azure.Cosmos
    
  2. 生成项目。

    dotnet build
    
  3. 返回 Visual Studio Code,再次导航到 Services/CosmosService.cs 文件。

  4. Microsoft.Azure.CosmosMicrosoft.Azure.Cosmos.Linq 命名空间添加新的 using 指令。

    using Microsoft.Azure.Cosmos;
    using Microsoft.Azure.Cosmos.Linq;
    
  5. 在 CosmosService 类中,添加 CosmosClient 类型的名为 _client 的新 private readonly 成员。

    private readonly CosmosClient _client;
    
  6. CosmosService 类新建一个空构造函数。

    public CosmosService()
    { }
    
  7. 在该构造函数中,创建 CosmosClient 类的新实例并传入一个字符串参数,其中包含前面你在实验中记下的“主连接字符串”值。 将此新实例存储在 _client 成员中。

    public CosmosService()
    { 
        _client = new CosmosClient(
            connectionString: "<primary-connection-string>"
        );
    }
    
  8. 回到 CosmosService 类中,创建一个名为 container 且类型为 Container 的新 private 属性。 设置 get 访问器以返回 cosmicworks 数据库和 products 容器。

    private Container container
    {
        get => _client.GetDatabase("cosmicworks").GetContainer("products");
    }
    
  9. 创建一个返回 IEnumerable<Product> 的名为 RetrieveAllProductsAsync 的新异步方法。

    public async Task<IEnumerable<Product>> RetrieveAllProductsAsync()
    { }
    
  10. 对于后续步骤,请在 RetrieveAllProductsAsync 方法中添加此代码。

    1. 使用 GetItemLinqQueryable<> 泛型方法获取 IQueryable<> 类型的对象,可以使用该对象构造语言集成查询 (LINQ)。 将该对象存储在名为 queryable 的变量中。

      var queryable = container.GetItemLinqQueryable<Product>();
      
    2. 使用 WhereOrderByDescending 扩展方法构造一个 LINQ 查询。 使用 ToFeedIterator 扩展方法创建一个用于从 Azure Cosmos DB 获取数据的迭代器,并将该迭代器存储在名为 feed 的变量中。 将整个表达式包装在 using 语句中,以便稍后释放该迭代器。

      using FeedIterator<Product> feed = queryable
          .Where(p => p.price < 2000m)
          .OrderByDescending(p => p.price)
          .ToFeedIterator();
      
    3. 使用泛型 List<> 类型创建一个名为 results 的新变量。

      List<Product> results = new();
      
    4. 创建一个 while 循环,该循环将迭代到 feed 变量的 HasMoreResults 属性返回 false 为止。 此循环将确保你循环访问服务器端结果的所有页面。

      while (feed.HasMoreResults)
      { }
      
    5. 在 while 循环中,以异步方式调用 feed 变量的 ReadNextAsync 方法并将结果存储在名为 response 的变量中。

      while (feed.HasMoreResults)
      {
          var response = await feed.ReadNextAsync();
      }
      
    6. 仍在 while 循环中,使用 foreach 循环来迭代响应中的每个项,并将其添加到 results 列表中。

      while (feed.HasMoreResults)
      {
          var response = await feed.ReadNextAsync();
          foreach (Product item in response)
          {
              results.Add(item);
          }
      }
      
    7. 返回 results 列表作为 RetrieveAllProductsAsync 方法的输出。

      return results;
      
  11. 创建一个返回 IEnumerable<Product> 的名为 RetrieveActiveProductsAsync 的新异步方法。

    public async Task<IEnumerable<Product>> RetrieveActiveProductsAsync()
    { }
    
  12. 对于后续步骤,请在 RetrieveActiveProductsAsync 方法中添加此代码。

    1. 使用 SQL 查询创建一个名为 sql 的新字符串,以检索其中已将一个筛选器 (@tagFilter) 应用于每个项的 tags 数组的多个字段。

      string sql = """
      SELECT
          p.id,
          p.name,
          p.category,
          p.sku,
          p.description,
          p.price
      FROM products p
      JOIN tag IN p.tags
      WHERE STRINGEQUALS(tag, @tagFilter, true)
      """;
      
    2. 创建名为 query 的新 QueryDefinition 变量,并传入 sql 字符串作为唯一一个查询参数。 另外,使用 WithParameter fluid 方法将值 red 应用于 @tagFilter 参数。

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

      using FeedIterator<Product> feed = container.GetItemQueryIterator<Product>(
          queryDefinition: query
      );
      
    4. 使用 while 循环来迭代多个结果页面,并将值存储在名为 results 的泛型 List<> 中。 返回 results 作为 RetrieveActiveProductsAsync 方法的输出。

      List<Product> results = new();
      
      while (feed.HasMoreResults)
      {
          FeedResponse<Product> response = await feed.ReadNextAsync();
          foreach (Product item in response)
          {
              results.Add(item);
          }
      }
      
      return results;
      
  13. 保存 Services/CosmosClient.cs 文件。

    提示

    如果你不确定代码是否正确,可以对照 GitHub 上的示例代码检查你的源代码。

验证最终应用程序

最后,在启用热重载的情况下运行应用程序。 运行该应用程序可以验证你的代码是否可以访问 API for NoSQL 中的数据。

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

    dotnet run
    
  2. run 命令的输出应包含运行该应用程序的端口和 URL 的列表。 打开新的浏览器并导航到正在运行的 Web 应用程序。 观察正在运行的应用程序的所有三个页面。 现在,每个页面都应该包含 Azure Cosmos DB 中的实时数据。

清理资源

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

后续步骤

使用 Azure Cosmos DB 创建第一个 .NET Web 应用程序后,接下来可以更深入地了解如何使用 SDK 导入更多数据、执行复杂查询和管理 Azure Cosmos DB for NoSQL 资源。