Udostępnij za pośrednictwem


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:

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.

  1. Otwórz terminal (ctrl+` w Visual Studio).

  2. Ustaw SupportTicketApiSupportTicketApi.Api jako bieżący katalog.

  3. 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.
  4. 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; }
    }
    
  5. 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.

  1. 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
    
  2. 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
    
  3. 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
    
  4. 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:

  5. 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:
      1. Pobiera odwołanie do usługi TicketContext od dostawcy usług.
      2. Wywołuje EnsureDatabaseAsync w celu utworzenia bazy danych, jeśli jeszcze nie istnieje.
      3. Wywołuje RunMigrationAsync, aby zastosować wszelkie oczekujące migracje.
      4. Wywołuje SeedDataAsync, aby zainicjować bazę danych z początkowymi danymi.
      5. Zatrzymuje pracownika przy użyciu StopApplication.
    • Metody EnsureDatabaseAsync, RunMigrationAsynci SeedDataAsync 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ń.

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.

  1. W projekcie SupportTicketApi.AppHost otwórz plik Program.cs.

  2. 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.

  1. W projekcie SupportTicketApi.Api otwórz plik Program.cs.

  2. 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.

  1. Uruchom aplikację i obserwuj pulpit nawigacyjny SupportTicketApi.

  2. Po krótkim oczekiwaniu stan usługi migrations będzie widoczny jako Zakończono.

    Zrzut ekranu przedstawiający panel sterowania .NET.NET Aspire z usługą migracji w stanie Zakończono.

  3. 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.