在 Entity Framework Core 中套用 .NET Aspire 遷移
由於 .NET.NET Aspire 專案使用容器化架構,因此資料庫是暫時的,而且可以隨時重新建立。 Entity Framework Core (EF Core) 會使用稱為 移轉 的功能來建立和更新資料庫架構。 由於應用程式啟動時會重新建立資料庫,因此您必須套用移轉,以在每次應用程式啟動時初始化資料庫架構。 這可藉由在您的應用程式中註冊在啟動期間執行移轉的移轉服務專案來完成。
在本教學課程中,您將瞭解如何設定 .NET Aspire 專案,以在應用程式啟動時執行 EF Core 移轉。
先決條件
若要使用 .NET.NET Aspire,您需要在本機安裝下列內容:
- .NET 8.0 或 .NET 9.0
- 符合 OCI 規範的容器運行時間,例如:
- 整合式開發人員環境 (IDE) 或程式碼編輯器,例如:
- Visual Studio 2022 17.9 版或更高版本 (選用)
-
Visual Studio Code (選擇性)
- C# Dev Kit:擴充功能(選擇性)
- JetBrains Rider 搭配 .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."
}
]
建立資料庫遷移
首先,建立一些要套用的移轉。
在 中開啟終端機 (+`Visual Studio )。
將 SupportTicketApiSupportTicketApi.Api 設定為目前目錄。
使用
dotnet ef
命令列工具 建立新的移轉,以擷取資料庫架構的初始狀態:dotnet ef migrations add InitialCreate --project ..\SupportTicketApi.Data\SupportTicketApi.Data.csproj
接下來的命令:
- 在 EF Core 目錄中執行 移轉命令行工具。
dotnet ef
會在此位置執行,因為 API 服務是資料庫內容的使用位置。 - 建立名為 InitialCreate的遷移。
- 在 SupportTicketApi.Data 專案中的 Migrations 資料夾中建立移轉。
- 在 EF Core 目錄中執行 移轉命令行工具。
修改模型,使其包含新的屬性。 開啟 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; } }
建立另一個新的遷移,以記錄模型的變更:
dotnet ef migrations add AddCompleted --project ..\SupportTicketApi.Data\SupportTicketApi.Data.csproj
現在您有一些遷移需要執行。 接下來,您將建立移轉服務,以在應用程式啟動時套用這些移轉。
建立移轉服務
若要在啟動時執行移轉,您必須建立套用移轉的服務。
將新的 Worker Service 專案新增至方案。 如果使用 Visual Studio,請在 [方案總管] 中以滑鼠右鍵點擊方案,然後選取 [Add>New Project]。 選擇 [Worker Service],並將項目命名為 SupportTicketApi.MigrationService。 如果使用命令列,請使用方案目錄中的下列命令:
dotnet new worker -n SupportTicketApi.MigrationService dotnet sln add SupportTicketApi.MigrationService
使用 SupportTicketApi.Data 或命令行,將 SupportTicketApi.ServiceDefaults 和 SupportTicketApi.MigrationService 項目參考新增至 Visual Studio 專案:
dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.Data dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.ServiceDefaults
請使用
或命令行將 Microsoft.EntityFrameworkCore.SqlServer NuGet 套件參考新增至 專案。 dotnet add package Aspire.Microsoft.EntityFrameworkCore.SqlServer
將反白顯示的行新增到 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();
在上述程式代碼中:
-
AddServiceDefaults
擴充方法 新增了服務預設功能。 -
AddOpenTelemetry
擴充方法 設定 OpenTelemetry 功能。 -
AddSqlServerDbContext
擴充方法會將TicketContext
服務新增至服務集合。 此服務可用來執行移轉並植入資料庫。
-
使用下列程式代碼取代 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
方法。 接著會執行下列步驟:- 從服務提供者取得
TicketContext
服務的參考。 - 呼叫
EnsureDatabaseAsync
,以在資料庫不存在時建立資料庫。 - 呼叫
RunMigrationAsync
以執行任何待處理的資料庫遷移。 - 呼叫
SeedDataAsync
,以初始數據植入資料庫。 - 用
StopApplication
停止工人。
- 從服務提供者取得
-
EnsureDatabaseAsync
、RunMigrationAsync
和SeedDataAsync
方法都會使用執行策略封裝其各自的資料庫作業,以處理與資料庫互動時可能發生的暫時性錯誤。 若要深入瞭解執行策略,請參閱 連線復原。
- 工人啟動時會呼叫
將移轉服務新增至協調器
移轉服務已建立,但它必須新增至 .NET.NET Aspire 應用程式主機,以便在應用程式啟動時執行。
在 SupportTicketApi.AppHost 專案中,開啟 Program.cs 檔案。
將下列突顯的程式碼新增至
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 專案中移除現有的數據植入程式代碼。
在 SupportTicketApi.Api 專案中,開啟 Program.cs 檔案。
刪除標示的行。
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(); } } }
測試移轉服務
現在已設定移轉服務,請執行應用程式來測試移轉。
取得程序代碼
您可以在
更多範例程序代碼
Aspire Shop 範例應用程式會使用此方法來套用移轉。 請參閱 AspireShop.CatalogDbManager
專案以了解服務移轉的實作。