Zastosuj migracje Entity Framework Core w .NET Aspire
Ponieważ projekty .NET.NET Aspire używają architektury konteneryzowanej, bazy danych są efemeryczne i można je odtworzyć w dowolnym momencie. Entity Framework Core (EF Core) używa funkcji o nazwie migrations do tworzenia i aktualizowania schematów bazy danych. Ponieważ bazy danych są tworzone ponownie podczas uruchamiania aplikacji, należy zastosować migracje w celu zainicjowania schematu bazy danych przy każdym uruchomieniu aplikacji. Jest to realizowane przez zarejestrowanie projektu usługi migracji w aplikacji, który uruchamia migracje podczas uruchamiania.
Z tego samouczka dowiesz się, jak skonfigurować projekty .NET Aspire do uruchamiania EF Core migracji podczas uruchamiania aplikacji.
Warunki wstępne
Aby pracować z .NET.NET Aspire, musisz mieć zainstalowane lokalnie następujące elementy:
- .NET 8.0 lub .NET 9.0
- Środowisko uruchomieniowe kontenera zgodne ze standardem OCI, takie jak:
- Docker Desktop lub Podman. Aby uzyskać więcej informacji, zobacz Container Runtime.
- Zintegrowane środowisko deweloperskie (IDE) lub edytor kodu, takie jak:
- Visual Studio 2022 w wersji 17.9 lub nowszej (opcjonalnie)
-
Visual Studio Code (opcjonalnie)
- C# Dev Kit: Rozszerzenie (opcjonalnie)
- JetBrains Rider z wtyczką .NET.NET Aspire (opcjonalnie)
Aby uzyskać więcej informacji, zobacz .NET.NET Aspire setup and toolingi .NET.NET Aspire SDK.
Pobierz aplikację startową
Ten samouczek wykorzystuje przykładową aplikację, która pokazuje, jak zastosować migracje EF Core w programie .NET Aspire. Użyj Visual Studio, aby sklonować aplikację przykładową z GitHub albo wprowadź następujące polecenie:
git clone https://github.com/MicrosoftDocs/aspire-docs-samples/
Przykładowa aplikacja znajduje się w folderze SupportTicketApi. Otwórz rozwiązanie w programie Visual Studio lub VS Code i pośmiń chwilę na przejrzenie przykładowej aplikacji i upewnij się, że działa przed kontynuowaniem. Przykładowa aplikacja jest prostą aplikacją programistyczną API do obsługi zgłoszeń serwisowych i zawiera następujące projekty:
- SupportTicketApi.Api: projekt ASP.NET Core hostujący interfejs API.
- SupportTicketApi.Data: zawiera konteksty i modele EF Core.
- SupportTicketApi.AppHost: zawiera hosta i konfigurację aplikacji .NET.NET Aspire.
- SupportTicketApi.ServiceDefaults: zawiera konfiguracje usługi domyślnej.
Uruchom aplikację, aby upewnić się, że działa zgodnie z oczekiwaniami. Na panelu sterowania .NET.NET Aspire wybierz punkt końcowy https swagger i przetestuj punkt końcowy GET /api/SupportTickets, rozwijając operację i wybierając opcję Wypróbuj. Wybierz Wykonaj, aby wysłać żądanie i wyświetlić odpowiedź:
[
{
"id": 1,
"title": "Initial Ticket",
"description": "Test ticket, please ignore."
}
]
Utwórz migracje
Zacznij od utworzenia niektórych migracji do zastosowania.
Otwórz terminal (ctrl+` w Visual Studio).
Ustaw SupportTicketApiSupportTicketApi.Api jako bieżący katalog.
Użyj narzędzia wiersza polecenia
dotnet ef
, aby utworzyć nową migrację w celu przechwycenia początkowego stanu schematu bazy danych:dotnet ef migrations add InitialCreate --project ..\SupportTicketApi.Data\SupportTicketApi.Data.csproj
Następujące polecenie:
- Uruchamia narzędzie wiersza poleceń dla migracji EF Core w katalogu SupportTicketApi.Api.
dotnet ef
jest uruchamiany w tej lokalizacji, ponieważ w usłudze interfejsu API jest używany kontekst bazy danych. - Tworzy migrację o nazwie InitialCreate.
- Tworzy migrację w folderze Migrations w projekcie SupportTicketApi.Data.
- Uruchamia narzędzie wiersza poleceń dla migracji EF Core w katalogu SupportTicketApi.Api.
Zmodyfikuj model tak, aby zawierał nową właściwość. Otwórz SupportTicketApi.DataModelsSupportTicket.cs i dodaj nową właściwość do klasy
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; } }
Utwórz kolejną nową migrację, aby przechwycić zmiany w modelu:
dotnet ef migrations add AddCompleted --project ..\SupportTicketApi.Data\SupportTicketApi.Data.csproj
Teraz masz pewne migracje do zastosowania. Następnie utworzysz usługę migracji, która stosuje te migracje podczas uruchamiania aplikacji.
Tworzenie usługi migracji
Aby przy starcie uruchomić migracje, należy utworzyć usługę, która te migracje zastosuje.
Dodaj nowy projekt Worker Service do rozwiązania. Jeśli używasz Visual Studio, kliknij rozwiązanie prawym przyciskiem myszy w Eksploratorze rozwiązań i wybierz pozycję Add>New Project. Wybierz Worker Service i nadaj projektowi nazwę SupportTicketApi.MigrationService. W przypadku korzystania z wiersza polecenia użyj następujących poleceń z katalogu rozwiązania:
dotnet new worker -n SupportTicketApi.MigrationService dotnet sln add SupportTicketApi.MigrationService
Dodaj odwołania do projektu SupportTicketApi.Data i SupportTicketApi.ServiceDefaults do projektu SupportTicketApi.MigrationService przy użyciu Visual Studio lub wiersza polecenia:
dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.Data dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.ServiceDefaults
Dodaj 📦Aspire. Microsoft.EntityFrameworkCore.SqlServer odwołanie do pakietu NuGet do projektu SupportTicketApi.MigrationService przy użyciu Visual Studio lub wiersza polecenia:
dotnet add package Aspire.Microsoft.EntityFrameworkCore.SqlServer
Dodaj wyróżnione wiersze do pliku Program.cs w projekcie 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();
W poprzednim kodzie:
- Metoda rozszerzenia
AddServiceDefaults
dodaje funkcje domyślne usługi. - Metoda rozszerzenia
AddOpenTelemetry
konfiguruje funkcje OpenTelemetry. - Metoda rozszerzenia
AddSqlServerDbContext
dodaje usługęTicketContext
do kolekcji usług. Ta usługa służy do uruchamiania migracji i wypełniania bazy danych.
- Metoda rozszerzenia
Zastąp zawartość pliku Worker.cs w projekcie SupportTicketApi.MigrationService następującym kodem:
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); }); } }
W poprzednim kodzie:
- Metoda
ExecuteAsync
jest wywoływana podczas rozpoczęcia pracy. Z kolei wykonuje następujące czynności:- Pobiera odwołanie do usługi
TicketContext
od dostawcy usług. - Wywołuje
EnsureDatabaseAsync
w celu utworzenia bazy danych, jeśli jeszcze nie istnieje. - Wywołuje
RunMigrationAsync
, aby zastosować wszelkie oczekujące migracje. - Wywołuje
SeedDataAsync
, aby zainicjować bazę danych z początkowymi danymi. - Zatrzymuje pracownika przy użyciu
StopApplication
.
- Pobiera odwołanie do usługi
- Metody
EnsureDatabaseAsync
,RunMigrationAsync
iSeedDataAsync
wszystkie hermetyzują odpowiednie operacje bazy danych przy użyciu strategii wykonywania w celu obsługi błędów przejściowych, które mogą wystąpić podczas interakcji z bazą danych. Aby dowiedzieć się więcej na temat strategii wykonywania, zobacz odporność połączeń.
- Metoda
Dodaj usługę migracji do koordynatora
Usługa migracji jest tworzona, ale należy ją dodać do hosta aplikacji .NET.NET Aspire, aby była uruchamiana po uruchomieniu aplikacji.
W projekcie SupportTicketApi.AppHost otwórz plik Program.cs.
Dodaj następujący wyróżniony kod do metody
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();
Spowoduje to rejestrację projektu SupportTicketApi.MigrationService jako usługi na hoście aplikacji .NET.NET Aspire.
Ważny
Jeśli używasz Visual Studioi wybrano opcję Enlist in Aspire orchestration podczas tworzenia projektu Worker Service, podobny kod jest dodawany automatycznie z nazwą usługi
supportticketapi-migrationservice
. Zastąp ten kod poprzednim kodem.
Usuń istniejący kod inicjowania
Ponieważ usługa migracji inicjuje bazę danych, należy usunąć istniejący kod inicjowania danych z projektu interfejsu API.
W projekcie SupportTicketApi.Api otwórz plik Program.cs.
Usuń wyróżnione wiersze.
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(); } } }
Testowanie usługi migracji
Po skonfigurowaniu usługi migracji uruchom aplikację, aby przetestować migracje.
Uruchom aplikację i obserwuj pulpit nawigacyjny SupportTicketApi.
Po krótkim oczekiwaniu stan usługi
migrations
będzie widoczny jako Zakończono.Wybierz link View w usłudze migracji, aby zbadać dzienniki pokazujące wykonane polecenia SQL.
Pobieranie kodu
Przykładową aplikację ukończoną można znaleźć w witrynie GitHub.
Więcej przykładowego kodu
Przykładowa aplikacja Aspire Shop używa tego podejścia do stosowania migracji. Zobacz projekt AspireShop.CatalogDbManager
dotyczący implementacji usługi migracji.