Dela via


Tillämpa migreringar av Entity Framework Core i .NET Aspire

Eftersom .NET.NET Aspire projekt använder en containerbaserad arkitektur är databaserna tillfälliga och kan återskapas när som helst. Entity Framework Core (EF Core) använder en funktion som kallas migreringar för att skapa och uppdatera databasscheman. Eftersom databaser återskapas när appen startas måste du använda migreringar för att initiera databasschemat varje gång appen startas. Detta uppnås genom att registrera ett migreringstjänstprojekt i din app som kör migreringar under starten.

I den här guiden får du lära dig hur du konfigurerar .NET Aspire projekt för att köra EF Core migreringar vid appstart.

Förutsättningar

Om du vill arbeta med .NET.NET Aspirebehöver du följande installerat lokalt:

Mer information finns i .NET.NET Aspire installation och verktygoch .NET.NET Aspire SDK.

Hämta startappen

I denna handledning används en exempelapp som visar hur du tillämpar EF Core migreringar i .NET Aspire. Använd Visual Studio för att klona exempelappen från GitHub eller använd följande kommando:

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

Exempelappen finns i mappen SupportTicketApi. Öppna lösningen i Visual Studio eller VS Code och ta en stund att granska exempelappen och se till att den körs innan du fortsätter. Exempelappen är ett rudimentärt supportbegäran-API och innehåller följande projekt:

  • SupportTicketApi.Api: Det ASP.NET Core projekt som hostar API:et.
  • SupportTicketApi.Data: Innehåller EF Core kontexter och modeller.
  • SupportTicketApi.AppHost: Innehåller .NET.NET Aspire applikationsvärd och konfiguration.
  • SupportTicketApi.ServiceDefaults: Innehåller standardkonfigurationerna för tjänsten.

Kör appen för att säkerställa att den fungerar som förväntat. På kontrollpanelen .NET.NET Aspire väljer du https Swagger-slutpunkten och testar API:ets GET /api/SupportTickets slutpunkt genom att expandera operationen och välja Prova det. Välj Kör för att skicka begäran och visa svaret:

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

Skapa migreringar

Börja med att skapa några migreringar att tillämpa.

  1. Öppna en terminal (Ctrl+` i Visual Studio).

  2. Ange SupportTicketApiSupportTicketApi.Api som aktuell katalog.

  3. Använd kommandoradsverktyget dotnet ef för att skapa en ny migrering för att avbilda databasschemats ursprungliga tillstånd:

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

    Kommandot för att fortsätta:

    • Kör kommandoradsverktyget för EF Core-migrering i katalogen SupportTicketApi.Api. dotnet ef körs på den här platsen eftersom API-tjänsten är den plats där DB-kontexten används.
    • Skapar en migrering med namnet InitialCreate.
    • Skapar migreringen i mappen Migrations i SupportTicketApi.Data-projektet.
  4. Ändra modellen så att den innehåller en ny egenskap. Öppna SupportTicketApi.DataModelsSupportTicket.cs och lägg till en ny egenskap i klassen 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. Skapa en ny migrering för att samla in ändringarna i modellen:

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

Nu har du några migreringar att tillämpa. Sedan skapar du en migreringstjänst som tillämpar dessa migreringar under appstarten.

Skapa migreringstjänsten

Om du vill köra migreringarna vid start måste du skapa en tjänst som tillämpar migreringarna.

  1. Lägg till ett nytt Worker Service projekt i lösningen. Om du använder Visual Studiohögerklickar du på lösningen i Solution Explorer och väljer Add>New Project. Välj Worker Service och ge projektet namnet SupportTicketApi.MigrationService. Om du använder kommandoraden använder du följande kommandon från lösningskatalogen:

    dotnet new worker -n SupportTicketApi.MigrationService
    dotnet sln add SupportTicketApi.MigrationService
    
  2. Lägg till SupportTicketApi.Data- och SupportTicketApi.ServiceDefaults-projektreferenserna i SupportTicketApi.MigrationService-projektet med hjälp av Visual Studio eller kommandoraden:

    dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.Data
    dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.ServiceDefaults
    
  3. Lägg till 📦Aspire. Microsoft.EntityFrameworkCore.SqlServer NuGet-paketreferens till SupportTicketApi.MigrationService-projektet med hjälp av Visual Studio eller kommandoraden:

    dotnet add package Aspire.Microsoft.EntityFrameworkCore.SqlServer
    
  4. Lägg till de markerade raderna i filen Program.cs i SupportTicketApi.MigrationService-projektet:

    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();
    

    I föregående kod:

  5. Ersätt innehållet i Worker.cs-filen i SupportTicketApi.MigrationService-projektet med följande kod:

    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);
            });
        }
    }
    

    I föregående kod:

    • Metoden ExecuteAsync anropas när arbetaren startar. Den utför i sin tur följande steg:
      1. Hämtar en referens till TicketContext-tjänsten från tjänstleverantören.
      2. Anropar EnsureDatabaseAsync för att skapa databasen om den inte finns.
      3. Anropar RunMigrationAsync för att tillämpa väntande migreringar.
      4. Anropar SeedDataAsync för att seeda databasen med inledande data.
      5. Stoppar arbetaren med StopApplication.
    • Metoderna EnsureDatabaseAsync, RunMigrationAsyncoch SeedDataAsync kapslar alla in sina respektive databasåtgärder med hjälp av körningsstrategier för att hantera tillfälliga fel som kan uppstå när de interagerar med databasen. För att lära dig mer om exekveringsstrategier, se Anslutningsåterhämtning.

Lägg till migreringstjänsten i orkestreraren

Migreringstjänsten skapas, men den måste läggas till i .NET.NET Aspire appvärd så att den körs när appen startar.

  1. Öppna filen SupportTicketApi.AppHost i Program.cs-projektet.

  2. Lägg till följande markerade kod i metoden 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();
    

    Detta registrerar SupportTicketApi.MigrationService-projektet som en tjänst i .NET.NET Aspire-applikationsvärd.

    Viktig

    Om du använder Visual Studio, och du valde alternativet Enlist in Aspire orchestration när du skapade Worker Service projektet, läggs liknande kod till automatiskt med tjänstnamnet supportticketapi-migrationservice. Ersätt koden med föregående kod.

Ta bort den befintliga utsädeskoden

Eftersom migreringstjänsten använder databasen bör du ta bort den befintliga datasåddkoden från API-projektet.

  1. Öppna filen SupportTicketApi.Api i Program.cs-projektet.

  2. Ta bort markerade rader.

    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();
            }
        }
    }
    

Testa migreringstjänsten

Nu när migreringstjänsten har konfigurerats kör du appen för att testa migreringarna.

  1. Kör appen och observera instrumentpanelen SupportTicketApi.

  2. Efter en kort väntan visas statusen för tjänsten migrations som Slutförd.

    En skärmbild av instrumentpanelen .NET.NET Aspire med migreringstjänsten i tillståndet Slutförd.

  3. Välj länken View i migreringstjänsten för att undersöka loggarna som visar DE SQL-kommandon som kördes.

Hämta koden

Du kan hitta den slutförda exempelappen på GitHub.

Mer exempelkod

Exempelappen Aspire Shop använder den här metoden för att tillämpa migreringar. Se AspireShop.CatalogDbManager projektet för implementeringen av migreringstjänsten.