共用方式為


在 Entity Framework Core 中套用 .NET Aspire 遷移

由於 .NET.NET Aspire 專案使用容器化架構,因此資料庫是暫時的,而且可以隨時重新建立。 Entity Framework Core (EF Core) 會使用稱為 移轉 的功能來建立和更新資料庫架構。 由於應用程式啟動時會重新建立資料庫,因此您必須套用移轉,以在每次應用程式啟動時初始化資料庫架構。 這可藉由在您的應用程式中註冊在啟動期間執行移轉的移轉服務專案來完成。

在本教學課程中,您將瞭解如何設定 .NET Aspire 專案,以在應用程式啟動時執行 EF Core 移轉。

先決條件

若要使用 .NET.NET Aspire,您需要在本機安裝下列內容:

如需詳細資訊,請參閱 .NET.NET Aspire 設定和工具.NET.NET Aspire SDK

取得入門應用程式

本教學課程使用範例應用程式,示範如何在 EF Core中套用 .NET Aspire 移轉。 使用 複製範例應用程式 或使用下列命令:

git clone https://github.com/MicrosoftDocs/aspire-docs-samples/

範例應用程式位於 SupportTicketApi 資料夾中。 在 Visual Studio 或 VS Code 中開啟解決方案,並花點時間檢閱範例應用程式,並確定它執行後再繼續。 範例應用程式是基本的支援票證 API,其中包含下列專案:

  • SupportTicketApi.Api:承載 API 的 ASP.NET Core 專案。
  • SupportTicketApi.Data:包含 EF Core 上下文和模型。
  • SupportTicketApi.AppHost:包含 .NET和.NET Aspire 的應用程式主機及設定。
  • SupportTicketApi.ServiceDefaults:包含預設服務組態。

執行應用程式以確保其如預期般運作。 從 .NET.NET Aspire 儀錶板中,選取 https Swagger 端點,然後展開作業並選取「試用」來測試 API 的 GET /api/SupportTickets 端點。選取「執行」以傳送要求並檢視回應。

[
  {
    "id": 1,
    "title": "Initial Ticket",
    "description": "Test ticket, please ignore."
  }
]

建立資料庫遷移

首先,建立一些要套用的移轉。

  1. 中開啟終端機 (+`Visual Studio )。

  2. SupportTicketApiSupportTicketApi.Api 設定為目前目錄。

  3. 使用 dotnet ef 命令列工具 建立新的移轉,以擷取資料庫架構的初始狀態:

    dotnet ef migrations add InitialCreate --project ..\SupportTicketApi.Data\SupportTicketApi.Data.csproj
    

    接下來的命令:

    • 在 EF Core 目錄中執行 移轉命令行工具。 dotnet ef 會在此位置執行,因為 API 服務是資料庫內容的使用位置。
    • 建立名為 InitialCreate的遷移。
    • SupportTicketApi.Data 專案中的 Migrations 資料夾中建立移轉。
  4. 修改模型,使其包含新的屬性。 開啟 SupportTicketApi.DataModelsSupportTicket.cs,並將新的屬性新增至 SupportTicket 類別:

    public sealed class SupportTicket
    {
        public int Id { get; set; }
        [Required]
        public string Title { get; set; } = string.Empty;
        [Required]
        public string Description { get; set; } = string.Empty;
        public bool Completed { get; set; }
    }
    
  5. 建立另一個新的遷移,以記錄模型的變更:

    dotnet ef migrations add AddCompleted --project ..\SupportTicketApi.Data\SupportTicketApi.Data.csproj
    

現在您有一些遷移需要執行。 接下來,您將建立移轉服務,以在應用程式啟動時套用這些移轉。

建立移轉服務

若要在啟動時執行移轉,您必須建立套用移轉的服務。

  1. 將新的 Worker Service 專案新增至方案。 如果使用 Visual Studio,請在 [方案總管] 中以滑鼠右鍵點擊方案,然後選取 [Add>New Project]。 選擇 [Worker Service],並將項目命名為 SupportTicketApi.MigrationService。 如果使用命令列,請使用方案目錄中的下列命令:

    dotnet new worker -n SupportTicketApi.MigrationService
    dotnet sln add SupportTicketApi.MigrationService
    
  2. 使用 SupportTicketApi.Data 或命令行,將 SupportTicketApi.ServiceDefaultsSupportTicketApi.MigrationService 項目參考新增至 Visual Studio 專案:

    dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.Data
    dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.ServiceDefaults
    
  3. 請使用 或命令行將 Microsoft.EntityFrameworkCore.SqlServer NuGet 套件參考新增至 專案。

    dotnet add package Aspire.Microsoft.EntityFrameworkCore.SqlServer
    
  4. 將反白顯示的行新增到 Program.cs 檔案中的 SupportTicketApi.MigrationService 項目:

    using SupportTicketApi.Data.Contexts;
    using SupportTicketApi.MigrationService;
    
    var builder = Host.CreateApplicationBuilder(args);
    
    builder.AddServiceDefaults();
    builder.Services.AddHostedService<Worker>();
    
    builder.Services.AddOpenTelemetry()
        .WithTracing(tracing => tracing.AddSource(Worker.ActivitySourceName));
    
    builder.AddSqlServerDbContext<TicketContext>("sqldata");
    
    var host = builder.Build();
    host.Run();
    

    在上述程式代碼中:

  5. 使用下列程式代碼取代 Worker.cs 項目中 SupportTicketApi.MigrationService 檔案的內容:

    using System.Diagnostics;
    
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Infrastructure;
    using Microsoft.EntityFrameworkCore.Storage;
    
    using OpenTelemetry.Trace;
    
    using SupportTicketApi.Data.Contexts;
    using SupportTicketApi.Data.Models;
    
    namespace SupportTicketApi.MigrationService;
    
    public class Worker(
        IServiceProvider serviceProvider,
        IHostApplicationLifetime hostApplicationLifetime) : BackgroundService
    {
        public const string ActivitySourceName = "Migrations";
        private static readonly ActivitySource s_activitySource = new(ActivitySourceName);
    
        protected override async Task ExecuteAsync(CancellationToken cancellationToken)
        {
            using var activity = s_activitySource.StartActivity("Migrating database", ActivityKind.Client);
    
            try
            {
                using var scope = serviceProvider.CreateScope();
                var dbContext = scope.ServiceProvider.GetRequiredService<TicketContext>();
    
                await EnsureDatabaseAsync(dbContext, cancellationToken);
                await RunMigrationAsync(dbContext, cancellationToken);
                await SeedDataAsync(dbContext, cancellationToken);
            }
            catch (Exception ex)
            {
                activity?.RecordException(ex);
                throw;
            }
    
            hostApplicationLifetime.StopApplication();
        }
    
        private static async Task EnsureDatabaseAsync(TicketContext dbContext, CancellationToken cancellationToken)
        {
            var dbCreator = dbContext.GetService<IRelationalDatabaseCreator>();
    
            var strategy = dbContext.Database.CreateExecutionStrategy();
            await strategy.ExecuteAsync(async () =>
            {
                // Create the database if it does not exist.
                // Do this first so there is then a database to start a transaction against.
                if (!await dbCreator.ExistsAsync(cancellationToken))
                {
                    await dbCreator.CreateAsync(cancellationToken);
                }
            });
        }
    
        private static async Task RunMigrationAsync(TicketContext dbContext, CancellationToken cancellationToken)
        {
            var strategy = dbContext.Database.CreateExecutionStrategy();
            await strategy.ExecuteAsync(async () =>
            {
                // Run migration in a transaction to avoid partial migration if it fails.
                await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
                await dbContext.Database.MigrateAsync(cancellationToken);
                await transaction.CommitAsync(cancellationToken);
            });
        }
    
        private static async Task SeedDataAsync(TicketContext dbContext, CancellationToken cancellationToken)
        {
            SupportTicket firstTicket = new()
            {
                Title = "Test Ticket",
                Description = "Default ticket, please ignore!",
                Completed = true
            };
    
            var strategy = dbContext.Database.CreateExecutionStrategy();
            await strategy.ExecuteAsync(async () =>
            {
                // Seed the database
                await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
                await dbContext.Tickets.AddAsync(firstTicket, cancellationToken);
                await dbContext.SaveChangesAsync(cancellationToken);
                await transaction.CommitAsync(cancellationToken);
            });
        }
    }
    

    在上述程式代碼中:

    • 工人啟動時會呼叫 ExecuteAsync 方法。 接著會執行下列步驟:
      1. 從服務提供者取得 TicketContext 服務的參考。
      2. 呼叫 EnsureDatabaseAsync,以在資料庫不存在時建立資料庫。
      3. 呼叫 RunMigrationAsync 以執行任何待處理的資料庫遷移。
      4. 呼叫 SeedDataAsync,以初始數據植入資料庫。
      5. StopApplication停止工人。
    • EnsureDatabaseAsyncRunMigrationAsyncSeedDataAsync 方法都會使用執行策略封裝其各自的資料庫作業,以處理與資料庫互動時可能發生的暫時性錯誤。 若要深入瞭解執行策略,請參閱 連線復原

將移轉服務新增至協調器

移轉服務已建立,但它必須新增至 .NET.NET Aspire 應用程式主機,以便在應用程式啟動時執行。

  1. SupportTicketApi.AppHost 專案中,開啟 Program.cs 檔案。

  2. 將下列突顯的程式碼新增至 ConfigureServices 方法:

    var builder = DistributedApplication.CreateBuilder(args);
    
    var sql = builder.AddSqlServer("sql")
                     .AddDatabase("sqldata");
    
    builder.AddProject<Projects.SupportTicketApi_Api>("api")
        .WithReference(sql);
    
    builder.AddProject<Projects.SupportTicketApi_MigrationService>("migrations")
        .WithReference(sql);
    
    builder.Build().Run();
    

    這會將 SupportTicketApi.MigrationService 項目編列為 .NET.NET Aspire 應用程式主機中的服務。

    重要

    如果您使用 Visual Studio,而且您在建立 Enlist in Aspire orchestration 項目時選取了 [Worker Service] 選項,系統會自動使用服務名稱 supportticketapi-migrationservice新增類似的程式代碼。 將該程式代碼用前面的程式代碼替換。

移除現有的播種代碼

由於移轉服務會植入資料庫,因此您應該從 API 專案中移除現有的數據植入程式代碼。

  1. SupportTicketApi.Api 專案中,開啟 Program.cs 檔案。

  2. 刪除標示的行。

    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    
        using (var scope = app.Services.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<TicketContext>();
            context.Database.EnsureCreated();
    
            if(!context.Tickets.Any())
            {
                context.Tickets.Add(new SupportTicket { Title = "Initial Ticket", Description = "Test ticket, please ignore." });
                context.SaveChanges();
            }
        }
    }
    

測試移轉服務

現在已設定移轉服務,請執行應用程式來測試移轉。

  1. 執行應用程式並觀察 SupportTicketApi 儀錶板。

  2. 短暫等候之後,migrations 服務狀態會顯示 完成

    .NET.NET Aspire 儀錶板的螢幕快照,其中移轉服務處於 [已完成] 狀態。

  3. 選取移轉服務上的 View 連結,以確認記錄中顯示已執行的 SQL 命令。

取得程序代碼

您可以在 上找到 已完成的範例應用程式。

更多範例程序代碼

Aspire Shop 範例應用程式會使用此方法來套用移轉。 請參閱 AspireShop.CatalogDbManager 專案以了解服務移轉的實作。