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 プラグイン (省略可能)
詳細については、
スターター アプリを入手する
このチュートリアルでは、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: 既定のサービス構成が含まれています。
アプリを実行して、期待どおりに動作することを確認します。
[
{
"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 ディレクトリで移行コマンド ライン ツール 実行します。 API サービスが DB コンテキストを使用する場所であるため、
dotnet ef
はこの場所で実行されます。 - InitialCreateという名前の移行を作成します。
- SupportTicketApi.Data プロジェクトの Migrations フォルダーにマイグレーションを作成します。
-
EF Core ディレクトリで移行コマンド ライン ツール 実行します。 API サービスが DB コンテキストを使用する場所であるため、
新しいプロパティが含まれるようにモデルを変更します。 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
📦 Aspireを追加します。Microsoft.EntityFrameworkCore.SqlServerSupportTicketApi.MigrationService またはコマンド ラインを使用して、Visual Studio プロジェクトへの 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(); } } }
移行サービスをテストする
移行サービスが構成されたので、アプリを実行して移行をテストします。
アプリを実行し、SupportTicketApi ダッシュボードを確認します。
しばらく待つと、
migrations
サービスの状態が 完了を表示します。移行サービスの View リンクを選択して、実行された SQL コマンドを示すログを調査します。
コードを取得する
完成したサンプル アプリは、GitHubにあります。
その他のサンプル コード
Aspire Shop サンプル アプリでは、このアプローチを使用して移行を適用します。 移行サービスの実装については、AspireShop.CatalogDbManager
プロジェクトを参照してください。
.NET Aspire