练习 - 使用数据库服务来保存 .NET Aspire 项目中的数据

已完成

在本练习中,你将替换公司正在开发的云原生应用程序的当前数据存储。 目前,该应用程序使用本地存储的 SQLite 数据库来存储目录数据,并使用内存中的 Redis 缓存来存储客户的购物车。 将现有数据存储替换为 PostgreSQL 和 MongoDB。

安装先决条件

.NET Aspire 的先决条件包括:

  • .NET 8
  • Visual Studio 2022 预览版
  • Docker Desktop 或 Podman
  • Visual Studio 中的 .NET Aspire 工作负载

如果已安装必备组件,则可以跳到克隆现有应用。

安装 .NET 8

单击此 .NET 8 链接,然后根据操作系统选择正确的安装程序。 例如,如果使用的是 Windows 11 和新式处理器,请选择适用于 Windows 的 x64 .NET 8 SDK。

下载完成后,运行安装程序并按照说明操作。 在终端窗口中,运行以下命令以验证安装是否成功:

dotnet --version

应会看到所安装的 .NET SDK 版本号。 例如:

8.0.300-preview.24203.14

安装 Visual Studio 2022 预览版

单击此 Visual Studio 2022 预览版链接,然后选择“下载预览版”。 下载完成后,运行安装程序并按照说明操作。

安装 Docker Desktop

单击此 Docker Desktop 链接,然后根据操作系统选择正确的安装程序。 下载完成后,运行安装程序并按照说明操作。

打开 Docker Desktop 应用程序并接受服务协议。

在 Visual Studio 中安装 .NET Aspire 工作负载

使用 .NET CLI 安装 .NET Aspire 工作负载:

  1. 打开终端。

  2. 使用以下命令安装 .NET Aspire 工作负载:

    dotnet workload update
    dotnet workload install aspire
    dotnet workload list
    

    应会看到 .NET Aspire 工作负载的详细信息。

     Installed Workload Id      Manifest Version      Installation Source
    ---------------------------------------------------------------------------------------------
    aspire                     8.0.0/8.0.100         SDK 8.0.300-preview.24203, VS 17.10.34902.84
    
    Use `dotnet workload search` to find additional workloads to install.
    

克隆和修改 Northern Mountains 应用

让我们使用 git 获取当前的 Northern Mountains 应用:

  1. 在命令行中,浏览到你选择的、可在其中处理代码的文件夹。

  2. 执行以下命令克隆 Northern Mountains eShop 示例应用程序

    git clone -b aspire-databases https://github.com/MicrosoftDocs/mslearn-aspire-starter
    
  3. 启动 Visual Studio,然后选择“打开项目或解决方案”

  4. 浏览到你克隆了 eShop 的文件夹,打开 start 文件夹并选择 eShop.databases.sln 文件,然后选择“打开”。

  5. 在“解决方案资源管理器”中,展开“eShop.AppHost”项目,然后打开“Program.cs”。

    // Databases
    
    var basketStore = builder.AddRedis("BasketStore").WithRedisCommander();
    
    // Identity Providers
    
    var idp = builder.AddKeycloakContainer("idp", tag: "23.0")
        .ImportRealms("../Keycloak/data/import");
    
    // DB Manager Apps
    
    builder.AddProject<Projects.Catalog_Data_Manager>("catalog-db-mgr");
    
    // API Apps
    
    var catalogApi = builder.AddProject<Projects.Catalog_API>("catalog-api");
    
    var basketApi = builder.AddProject<Projects.Basket_API>("basket-api")
            .WithReference(basketStore)
            .WithReference(idp);
    
    // Apps
    
    // Force HTTPS profile for web app (required for OIDC operations)
    var webApp = builder.AddProject<Projects.WebApp>("webapp")
        .WithReference(catalogApi)
        .WithReference(basketApi)
        .WithReference(idp, env: "Identity__ClientSecret");
    

    上面的代码显示了应用程序的当前配置。 该应用使用 Redis 缓存来存储购物车。

  6. 浏览应用的其余部分,重点关注 Catalog.Data.ManagerCatalog.API 项目,并了解它们如何使用本地存储的 SQLite 数据库。

  7. 若要启动应用,请按 F5 或选择“调试 > 开始调试”。

  8. 如果出现“启动 Docker Desktop”对话框,请选择“是”

  9. 出现 eShop .NET Aspire 仪表板时,对于 webapp 资源,请选择安全终结点:

    eShop .NET Aspire 仪表板的屏幕截图。突出显示 webapp 终结点。

  10. 应用将在浏览器中打开。 可以浏览应用并查看其工作原理。

    eShop 主页的屏幕截图。

    测试用户凭据为 test@example.comP@$$w0rd1

  11. 若要停止调试,请按 Shift+F5,或选择“调试 > 停止调试”。

添加 .NET Aspire PostgreSQL 组件

负责目录微服务的团队构建了该应用程序以使用本地存储的 SQLite 数据库。 此方法适用于开发,但团队希望使用更可靠的数据库进行生产。

有两个项目连接到 SQLite 数据库,即 Catalog.Data.ManagerCatalog.API 项目。 数据管理器仅用于向数据库添加数据,因此应该重点关注 Catalog.API 项目。

  1. 在“解决方案资源管理器”中,右键单击“Catalog.API”项目,然后选择“添加”>“.NET Aspire 包”。

  2. 在“搜索”框中,将 Npgsql.EntityFramework 添加到末尾,然后按 Enter

  3. 在左侧的结果中,选择“Aspire.Npgsql.EntityFrameworkCore.PostgreSQL”。

  4. 在右侧,选择版本下拉菜单,然后选择最新的“8.0.0”版本。

  5. 选择“安装” 。

  6. 如果出现“预览更改”对话框,请选择“应用”。

  7. 在“许可接受”对话框中,选择“我接受”

  8. 在“解决方案资源管理器”中,选择“Catalog.API”项目以查看 Catalog.API.csproj 文件的内容。

  9. 删除 Microsoft.EntityFrameworkCore.SqlitePackageReference

    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.3" />
    

注册新的 PostgreSQL DbContext

  1. 在“解决方案资源管理器”中,展开“Catalog.API”项目,然后打开“Program.cs”文件。

  2. 替换 SQLite DbContext:

    builder.Services.AddDbContext<CatalogDbContext>(
         options => options.UseSqlite(builder.Configuration.GetConnectionString("sqlconnection")
     	    ?? throw new InvalidOperationException(
     		    "Connection string 'sqlconnection' not found.")));
    

    使用新的 PostgreSQL DbContext:

    builder.AddNpgsqlDbContext<CatalogDbContext>("CatalogDB");
    

    该应用不再需要读取 Database.db 文件,因此请删除 appsettings.json 中的关联字符串。

  3. 在“解决方案资源管理器”的“catalog.API”下,选择“appsettings.json”。

  4. 删除 ConnectionStrings 条目,该文件现在将如下所示:

    {
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning"
        }
      },
      "OpenApi": {
        "Endpoint": {
          "Name": "Catalog.API v1"
        },
        "Document": {
          "Description": "The Catalog Microservice HTTP API. This is a Data-Driven/CRUD microservice sample",
          "Title": "eShop - Catalog HTTP API",
          "Version": "v1"
        }
      },
      "CatalogOptions": {
        "PicBasePathFormat": "items/{0}/pic/"
      }
    }
    
    
  5. 右键单击“Catalog.Data.Manager”项目,然后选择“删除”。

  6. 在对话框中选择“确定”

数据库团队会创建 PostgreSQL 数据库备份,用于创建目录数据库并为其设定种子。 可以在“Catalog.API/Seed”文件夹中查看备份。

使用绑定卷为 PostgreSQL 数据库设定种子

AppHost 项目可以创建一个 PostgreSQL 数据库容器,使用来自绑定卷的数据为其设定种子,然后通过依赖项注入将引用传递到 Catalog.API

  1. 在“解决方案资源管理器”中,右键单击“eShop.AppHost”项目,然后选择“添加”>“.NET Aspire 包”。

  2. 在“搜索”框中,将 PostgreSQL 添加到末尾,然后按 Enter

  3. 在左侧的结果中选择“Aspire.Hosting.PostgreSQL”。

  4. 在右侧,选择版本下拉菜单,然后选择最新的“8.0.0”版本。

  5. 选择“安装” 。

  6. 如果出现“预览更改”对话框,请选择“应用”。

  7. 在“许可接受”对话框中,选择“我接受”

  8. 在“解决方案资源管理器”中,展开“eShop.AppHost”项目,然后打开“Program.cs”文件。

  9. //Databases 注释下,添加以下代码:

    // Databases
    
    var basketStore = builder.AddRedis("BasketStore").WithRedisCommander();
    var postgres = builder.AddPostgres("postgres")
        .WithEnvironment("POSTGRES_DB", "CatalogDB")
        .WithBindMount("../Catalog.API/Seed", "/docker-entrypoint-initdb.d").WithPgAdmin();
    var catalogDB = postgres.AddDatabase("CatalogDB");
    

    上述代码将创建一个 PostgreSQL 数据库容器,添加一个名为 CatalogDB 的数据库,并将 /docker-entrypoint-initdb.d 目录绑定到 ../Catalog.API/Seed 目录。 该代码还会为 pgAdmin 工具创建一个容器,用于管理 PostgreSQL 数据库。

  10. 通过添加 .WithReference(catalogDB)catalogDB 引用传递给 Catalog.API 项目,代码现在如下所示:

    // API Apps
    
    var catalogApi = builder.AddProject<Projects.Catalog_API>("catalog-api")
      .WithReference(catalogDB); 
    
  11. 不再需要 Catalog.Data.Manager 项目,因此请将其从 AppHost 中删除。 删除此代码:

    // DB Manager Apps
    
    builder.AddProject<Projects.Catalog_Data_Manager>("catalog-db-mgr");
    

测试应用程序

使用 .NET Aspire 允许团队删除整个项目。 此外,目录 API 只需要一行代码即可添加 PostgresQL 数据库上下文。 来自 AppHost 的依赖项注入和服务发现意味着不需要其他代码更改即可允许 API 连接到新的数据库。

  1. 编译并启动应用,按 F5 或选择“调试 > 开始调试”。

    显示更新后的 .NET Aspire 仪表板的屏幕截图,其中突出显示了两个新的 PostgreSQL 容器。

    仪表板中有两个新容器,用于托管 PostgreSQL 数据库服务器和 pgAdmin 工具。 还有一个用于托管 CatalogDB 数据库的 PostgreSQL 数据库资源。

  2. 使用 pgAdmin 连接到 PostgreSQL 数据库并浏览数据。 选择 postgres pgadmin 终结点。

    pgAdmin 界面的屏幕截图,其中突出显示了目录表的导航。

  3. 展开“aspire 实例”>“postgres”>“数据库”>“CatalogDB”>“架构”>“目录”>“”。 然后右键单击“目录”表,然后选择“查看/编辑数据”>“前 100 行”。

  4. 可以看到 AppHost 加载的数据。

    pgAdmin 界面的屏幕截图,其中显示了目录表中返回的行。

  5. 在浏览器中选择“eShop 资源”仪表板选项卡,然后选择 webapp 终结点。

  6. 应用将打开并像往常一样工作。

  7. 若要停止调试,请按 Shift+F5,或选择“调试 > 停止调试”。

将 .NET Aspire MongoDB 组件添加到应用

当前应用使用 Redis 作为客户购物车的内存数据存储。 团队希望为购物车使用更可靠且持久的数据存储。 将 Redis 缓存替换为 MongoDB 数据库。

将 Basket.API 更改为使用 MongoDB

  1. 在“解决方案资源管理器”中,右键单击“Basket.API”项目,选择“添加”,然后选择“添加”>“.NET Aspire 包”。
  2. 在“搜索”框中,在末尾输入 MongoDB,然后按 Enter
  3. 选择“Aspire.MongoDB.Driver”,然后选择最新的“8.0.0”版本。
  4. 选择“安装” 。
  5. 如果出现“预览更改”对话框,请选择“应用”。
  6. 在“许可接受”对话框中,选择“我接受”。@

创建 MongoDB 购物车存储

购物车微服务使用 HostingExtensions 来管理 Redis 数据存储。 将 Redis 数据存储替换为 MongoDB 数据存储。

  1. 在“解决方案资源管理器”中,展开“Basket.API”项目,选择“存储”文件夹,然后选择“RedisBasketStore.cs”文件。

    有两种异步方法(GetBasketAsyncUpdateBasketAsync)使用 Redis 缓存。 让我们来创建这些方法的 MongoDB 版本。

  2. 在“解决方案资源管理器”中,右键单击“存储”文件夹,然后选择“添加”>“”。

  3. 在“添加新项”对话框中,将文件命名为“MongoBasketStore.cs”,然后选择“添加”。

  4. MongoBasketStore.cs 文件中的代码替换为以下代码:

    using eShop.Basket.API.Models;
    using MongoDB.Driver;
    using MongoDB.Driver.Linq;
    
    namespace eShop.Basket.API.Storage;
    
    public class MongoBasketStore
    {
      private readonly IMongoCollection<CustomerBasket> _basketCollection;
    
      public MongoBasketStore(IMongoClient mongoClient)
      {
        // The database name needs to match the created database in the AppHost
        _basketCollection = mongoClient.GetDatabase("BasketDB").GetCollection<CustomerBasket>("basketitems");
      }
    
      public async Task<CustomerBasket?> GetBasketAsync(string customerId)
      {
        var filter = Builders<CustomerBasket>.Filter.Eq(r => r.BuyerId, customerId);
    
        return await _basketCollection.Find(filter).FirstOrDefaultAsync();
      }
    
      public async Task<CustomerBasket?> UpdateBasketAsync(CustomerBasket basket)
      {
        var filter = Builders<CustomerBasket>.Filter.Eq(r => r.BuyerId, basket.BuyerId);
    
        var result = await _basketCollection.ReplaceOneAsync(filter, basket, new ReplaceOptions { IsUpsert = true });
    
        return result.IsModifiedCountAvailable ? basket : null;
      }
    }
    

    上述代码将创建一个 MongoBasketStore 类,该类适用于 CustomerBasket 模型。 该集合处理针对 MongoDB 数据库中客户购物车的 CRUD 操作。

  5. 在“解决方案资源管理器”中,展开“Basket.API”>“扩展”,然后选择“HostingExtensions.cs”文件。

  6. 替换 Redis 代码:

    builder.AddRedis("BasketStore");
    
    builder.Services.AddSingleton<RedisBasketStore>();
    

    使用 MongoDB 代码:

    builder.AddMongoDBClient("BasketDB");
    
    builder.Services.AddSingleton<MongoBasketStore>();
    
  7. 在“解决方案资源管理器”中,展开“Grpc”文件夹,然后打开“BasketService.cs”文件。

  8. 更改类以接受 MongoBasketStore,替换:

    public class BasketService(RedisBasketStore basketStore) : Basket.BasketBase
    

    替换为:

    public class BasketService(MongoBasketStore basketStore) : Basket.BasketBase
    

将 MongoDB 数据库添加到 AppHost

  1. 在“解决方案资源管理器”中,右键单击“eShop.AppHost”项目,然后选择“添加”>“.NET Aspire 包”。

  2. 在“搜索”框中,在末尾输入 MongoDB,然后按 Enter

  3. 选择“ Aspire.Hosting.MongoDB”,然后选择最新的“8.0.0”版本。

  4. 选择“安装” 。

  5. 如果出现“预览更改”对话框,请选择“应用”。

  6. 在“许可接受”对话框中,选择“我接受”。@

  7. 在“解决方案资源管理器”中,展开“eShop.AppHost”项目,然后打开“Program.cs”文件。

  8. 在“数据库”部分中,添加 MongoDB 组件:

    var mongo = builder.AddMongoDB("mongo")
      .WithMongoExpress()
      .AddDatabase("BasketDB");
    

    上述代码将创建一个 MongoDB 数据库容器,并添加名为 BasketDB的数据库。 该代码还会为 Mongo Express 工具创建一个容器,用于管理 MongoDB 数据库。

  9. 删除 Redis 容器:

    var basketStore = builder.AddRedis("BasketStore").WithRedisCommander();
    

    代码现在应如下所示:

    // Databases
    
    var postgres = builder.AddPostgres("postgres")
        .WithEnvironment("POSTGRES_DB", "CatalogDB")
        .WithBindMount("../Catalog.API/Seed", "/docker-entrypoint-initdb.d")
        .WithPgAdmin();
    var catalogDB = postgres.AddDatabase("CatalogDB");
    
    var mongo = builder.AddMongoDB("mongo")
        .WithMongoExpress()
        .AddDatabase("BasketDB");
    
  10. Basket.API 项目需要对新的 MongoDB 数据库的引用,应该移除 Redis 引用:

    var basketApi = builder.AddProject<Projects.Basket_API>("basket-api")
            .WithReference(mongo)
            .WithReference(idp);
    

Basket.API 项目现已准备好使用 MongoDB 数据库。 让我们测试应用,看看它是否有效。

测试应用程序

  1. 编译并启动应用,按 F5 或选择“调试 > 开始调试”。

    .NET Aspire 仪表板的屏幕截图,其中突出显示了 MongoDB 容器。

    可以在仪表板中看到新的 MongoDB 容器,一个用于数据库服务器,另一个用于 Mongo Express。 还有一个新的 MongoDBDatabase 资源,用于托管 BasketDB 数据库。

  2. 选择 webapp 终结点。

  3. 要使用测试用户凭据登录,请选择右上角的用户图标。 电子邮件地址是 test@example.com,密码是 P@$$w0rd1

  4. 从主页中选择 Adventurer GPS Watch

  5. 选择“添加到购物袋”,应该会看到一个异常:

    显示 RpcException 的屏幕截图。

调试应用

当你尝试向购物车添加项目时,应用将引发异常。 可以使用仪表板来帮助调试问题。

  1. 在浏览器中选择“eShop 资源”仪表板选项卡。

    仪表板的屏幕截图,其中突出显示了 Basket.API 和 webapp 中的错误。

    仪表板显示 basket-apiwebapp 中存在错误。 查看 basket-api 的日志。

  2. 对于“basket-api”资源,在“日志”列中,选择“查看”。

    basket-api 服务的日志的屏幕截图。

    有一个异常:

    System.FormatException: Element '_id' does not match any field or property of class eShop.Basket.API.Models.CustomerBasket.
    
  3. 选择“资源”菜单项,然后选择 mongo-mongoexpress 终结点。

  4. 在“数据库”部分的 BasketDB 旁边,选择“查看”。

  5. 在“集合”中的“购物车项”旁边,选择“查看”。

    Mongo Express 的屏幕截图,其中显示了存储在购物车集合中的数据。

    存储在 MongoDB 中的文档具有“_id”字段。 存储在 MongoDB 集合中的每个文档都必须具有唯一的“_id”字段。

  6. 若要停止调试,请按 Shift+F5,或选择“调试 > 停止调试”。

查看代码并修复问题

让我们查看一下 CustomerBasket,看看能否找到问题。

  1. 在“解决方案资源管理器”中,展开“Basket.API”>“模型”文件夹,然后打开“CustomerBasket.cs”文件。

    public class CustomerBasket
    {
        public required string BuyerId { get; set; }
    
        public List<BasketItem> Items { get; set; } = [];
    }
    

    CustomerBasket 模型没有与“_id”字段匹配的字段或属性。 实体框架正在尝试将“_id”字段映射到 CustomerBasket 模型,但找不到匹配项。

  2. 更新 CustomerBasket 模型以包含“_id”字段:

    public class CustomerBasket
    {
        /// <summary>
        /// MongoDB document identifier
        /// </summary>
        public string _id { get; set; } = "";
    
        public required string BuyerId { get; set; }
    
        public List<BasketItem> Items { get; set; } = [];
    }
    

测试固定应用

  1. 若要编译并启动应用,请按 F5 或选择“调试 > 开始调试”。

  2. 对于 webapp,在“终结点”列中,右键单击 URL,然后选择“在 InPrivate 窗口中打开链接”。

    使用 InPrivate 窗口可确保浏览器不会使用以前的会话 Cookie 进行身份验证。

  3. 要使用测试用户凭据登录,请选择右上角的用户图标。 电子邮件地址是 test@example.com,密码是 P@$$w0rd1

  4. 从主页中选择 Adventurer GPS Watch

  5. 选择“添加到购物袋”。

    eShop 购物车运行的屏幕截图。

    Northern Mountains 应用购物车功能正在运行。

已成功将 SQLite 数据库替换为 PostgreSQL 数据库,并将 Redis 缓存替换为 MongoDB 数据库。 你使用 .NET Aspire 管理数据库并浏览其中的数据,并使用仪表板帮助调试应用的问题。