Dela via


Tillämpa migreringar av Entity Framework Core i Aspire

Eftersom 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 Aspire projekt för att köra EF Core migreringar vid appstart.

Prerequisites

För att arbeta med Aspirebehöver du följande installerat lokalt:

För mer information, se Aspire konfiguration och verktyg och Aspire SDK.

Hämta startappen

I denna handledning används en exempelapp som visar hur du tillämpar EF Core migreringar i 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.AppHost: Innehåller Aspire AppHost och konfigurationen.
  • SupportTicketApi.Data: Innehåller EF Core kontexter och modeller.
  • 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. Vänta i Aspire Dashboard tills alla resurser körs och är felfria. Välj sedan https Swagger-slutpunkten och testa API:ets GET /api/SupportTickets slutpunkt genom att expandera åtgärden och välja Prova. Välj Kör för att skicka begäran och visa svaret:

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

Stäng webbläsarflikarna som visar Swagger-slutpunkten och Aspire instrumentpanelen och sluta sedan felsöka.

Skapa migreringar

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

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

  2. Ange SupportTicketApi\SupportTicketApi.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 är:

    • 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.
  1. Ändra modellen så att den innehåller en ny egenskap. Öppna SupportTicketApi.Data\Models\SupportTicket.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; }
    }
    
  2. 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 genomföra. Sedan skapar du en migreringstjänst som tillämpar dessa migreringar under appstarten.

Felsöka migreringsproblem

När du arbetar med EF Core migreringar i Aspire projekt kan det uppstå några vanliga problem. Här är lösningar på de vanligaste problemen:

Felet "Ingen databasprovider har konfigurerats"

Om du får ett felmeddelande som "Ingen databasprovider har konfigurerats för denna DbContext" när du kör migreringskommandon beror det på att EF-verktygen inte kan hitta en anslutningssträng eller databasproviderkonfiguration. Detta beror på att Aspire projekt använder tjänsteupptäckt och orkestrering som endast är tillgängligt under körning.

Lösning: Lägg tillfälligt till en anslutningssträng i projektets appsettings.json fil:

  1. Öppna eller skapa en appsettings.json fil i DITT API-projekt (där DbContext är registrerad).

  2. Lägg till en anslutningssträng med samma namn som används i din Aspire AppHost:

    {
      "ConnectionStrings": {
        "ticketdb": "Server=(localdb)\\mssqllocaldb;Database=TicketDb;Trusted_Connection=true"
      }
    }
    
  3. Kör dina migreringskommandon som vanligt.

  4. Ta bort anslutningssträngen från appsettings.json när du är klar, vilket Aspire ger den vid körning.

Tip

Anslutningssträngens namn måste matcha det du använder i din AppHost. Om du till exempel använder builder.AddProject<Projects.SupportTicketApi_Api>().WithReference(sqlServer.AddDatabase("ticketdb"))använder du "ticketdb" som anslutningssträngsnamn.

Flera databaser i en lösning

När lösningen Aspire har flera tjänster med olika databaser skapar du migreringar för varje databas separat:

  1. Navigera till varje tjänstprojektkatalog som har en DbContext.

  2. Kör migreringskommandon med lämplig projektreferens:

    # For the first service/database
    dotnet ef migrations add InitialCreate --project ..\FirstService.Data\FirstService.Data.csproj
    
    # For the second service/database  
    dotnet ef migrations add InitialCreate --project ..\SecondService.Data\SecondService.Data.csproj
    
  3. Skapa separata migreringstjänster för varje databas eller hantera flera DbContexts i en enda migreringstjänst.

Konfiguration av startprojekt

Kontrollera att du kör migreringskommandon från rätt projekt:

  • CLI: Navigera till projektkatalogen som innehåller DbContext-registreringen (vanligtvis ditt API-projekt)
  • Package Manager-konsolen: Ställ in startprojektet på det som konfigurerar DbContext och standardprojektet där migreringar ska skapas

Skapa migreringstjänsten

Om du vill köra migreringar anropar du EF CoreMigrate metoden eller MigrateAsync metoden. I den här självstudien skapar du en separat arbetstjänst för att tillämpa migreringar. Den här metoden separerar migreringsproblem i ett dedikerat projekt, vilket är enklare att underhålla och gör att migreringar kan köras innan andra tjänster startas.

Note

Var du skapar migreringar: Migreringar ska skapas i projektet som innehåller dina Entity Framework DbContext- och modellklasser (i det här exemplet SupportTicketApi.Data). Migreringstjänstprojektet refererar till det här dataprojektet för att tillämpa migreringarna vid start.

Så här skapar du 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, namnge projektet SupportTicketApi.MigrationService och mål .NET 9.0. Om du använder kommandoraden använder du följande kommandon från lösningskatalogen:

    dotnet new worker -n SupportTicketApi.MigrationService -f "net9.0"
    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:

    cd SupportTicketApi.MigrationService
    dotnet add package Aspire.Microsoft.EntityFrameworkCore.SqlServer -v "9.4.0"
    

    Tip

    I vissa fall kan du också behöva lägga 📦 till paketet Microsoft.EntityFrameworkCore.Tools för att förhindra EF Core att tyst misslyckas utan att använda migreringar. Detta är särskilt relevant när du använder andra databaser än SQL Server, till exempel PostgreSQL. Mer information finns i dotnet/efcore#27215.

  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 RunMigrationAsync(dbContext, cancellationToken);
                await SeedDataAsync(dbContext, cancellationToken);
            }
            catch (Exception ex)
            {
                activity?.AddException(ex);
                throw;
            }
    
            hostApplicationLifetime.StopApplication();
        }
    
        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 dbContext.Database.MigrateAsync(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. Kallar på RunMigrationAsync för att tillämpa väntande migreringar.
      3. Anropar SeedDataAsync för att seeda databasen med inledande data.
      4. Stoppar arbetaren med StopApplication.
    • Metoderna RunMigrationAsync och SeedDataAsync inkapslar sina respektive databasåtgärder med strategier 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 Aspire AppHost så att den körs när appen startar.

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

  2. Lägg till följande markerade kod:

    var builder = DistributedApplication.CreateBuilder(args);
    
    var sql = builder.AddSqlServer("sql", port: 14329)
                     .WithEndpoint(name: "sqlEndpoint", targetPort: 14330)
                     .AddDatabase("sqldata");
    
    var migrations = builder.AddProject<Projects.SupportTicketApi_MigrationService>("migrations")
        .WithReference(sql)
        .WaitFor(sql);
    
    builder.AddProject<Projects.SupportTicketApi_Api>("api")
        .WithReference(sql)
        .WithReference(migrations)
        .WaitForCompletion(migrations);
    
    builder.Build().Run();
    

    Den här koden registrerar SupportTicketApi.MigrationService projektet som en tjänst i Aspire AppHost. Det säkerställer också att API-resursen inte körs förrän migreringarna har slutförts.

    Note

    I föregående kod lägger anropet till en representation av en AddDatabase-databas till SQL Server-applikationsmodellen med en anslutningssträng. Den skapar ingen databas i containern SQL Server . För att säkerställa att databasen skapas, anropar exempelprojektet metoden EF CoreEnsureCreated från SUPPORT-API:ets Program.cs-fil.

    Tip

    Koden skapar containern varje gång den SQL Server körs och tillämpar migreringar på den. Data sparas inte mellan felsökningssessioner och eventuella nya databasrader som du skapar under testningen kommer inte att överleva en omstart av appen. Om du vill spara dessa data lägger du till en datavolym i containern. Mer information finns i Lägga till SQL Server resurs med datavolym.

  3. Om koden inte kan matcha migreringstjänstprojektet lägger du till en referens till migreringstjänstprojektet i AppHost-projektet:

    dotnet add SupportTicketApi.AppHost reference SupportTicketApi.MigrationService
    

    Important

    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.

Scenario med flera databaser

Om lösningen Aspire använder flera databaser har du två alternativ för att hantera migreringar:

Skapa en dedikerad migreringstjänst för varje databas. Den här metoden ger bättre isolering och gör det enklare att hantera olika databasscheman oberoende av varandra.

  1. Skapa separata migreringstjänstprojekt för varje databas:

    dotnet new worker -n FirstService.MigrationService -f "net8.0"
    dotnet new worker -n SecondService.MigrationService -f "net8.0"
    
  2. Konfigurera varje migreringstjänst för att hantera dess specifika databaskontext.

  3. Lägg till båda migreringstjänsterna i din AppHost:

    var firstDb = sqlServer.AddDatabase("firstdb");
    var secondDb = postgres.AddDatabase("seconddb");
    
    var firstMigrations = builder.AddProject<Projects.FirstService_MigrationService>()
        .WithReference(firstDb);
    
    var secondMigrations = builder.AddProject<Projects.SecondService_MigrationService>()
        .WithReference(secondDb);
    
    // Ensure services wait for their respective migrations
    builder.AddProject<Projects.FirstService_Api>()
        .WithReference(firstDb)
        .WaitFor(firstMigrations);
    
    builder.AddProject<Projects.SecondService_Api>()
        .WithReference(secondDb)
        .WaitFor(secondMigrations);
    

Alternativ 2: Enskild migreringstjänst med flera kontexter

Du kan också skapa en migreringstjänst som hanterar flera databaskontexter:

  1. Lägg till referenser till alla dataprojekt i migreringstjänsten.

  2. Registrera alla DbContexts i migreringstjänstens Program.cs.

  3. Ändra Worker.cs för att tillämpa migreringar på varje kontext:

    public async Task<bool> RunMigrationAsync(IServiceProvider serviceProvider)
    {
        await using var scope = serviceProvider.CreateAsyncScope();
    
        var firstContext = scope.ServiceProvider.GetRequiredService<FirstDbContext>();
        var secondContext = scope.ServiceProvider.GetRequiredService<SecondDbContext>();
    
        await firstContext.Database.MigrateAsync();
        await secondContext.Database.MigrateAsync();
    
        return true;
    }
    

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 Aspire instrumentpanelen med migreringstjänsten i tillståndet Slutförd.

  3. Välj ikonen Console logs 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.