Delen via


Pas migraties van Entity Framework Core toe in .NET Aspire

Aangezien .NET.NET Aspire projecten een containerarchitectuur gebruiken, zijn databases kortstondig en kunnen ze op elk gewenst moment opnieuw worden gemaakt. Entity Framework Core (EF Core) maakt gebruik van een functie met de naam migraties voor het maken en bijwerken van databaseschema's. Omdat databases opnieuw worden gemaakt wanneer de app wordt gestart, moet u migraties toepassen om het databaseschema te initialiseren telkens wanneer uw app wordt gestart. Dit wordt bereikt door een migratieserviceproject te registreren in uw app die migraties uitvoert tijdens het opstarten.

In deze zelfstudie leert u hoe u .NET Aspire projecten configureert om EF Core migraties uit te voeren tijdens het opstarten van de app.

Voorwaarden

Als u met .NET.NET Aspirewilt werken, hebt u het volgende lokaal geïnstalleerd:

Zie .NET.NET Aspire setup en hulpprogramma'sen .NET.NET Aspire SDK-voor meer informatie.

De starter-app verkrijgen

In deze handleiding wordt een voorbeeldapp gebruikt om te laten zien hoe u EF Core-migraties in .NET Aspirekunt toepassen. Gebruik Visual Studio om de voorbeeld-app te klonen vanuit GitHub of gebruik de volgende opdracht:

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

De voorbeeld-app bevindt zich in de map SupportTicketApi. Open de oplossing in Visual Studio of VS Code en neem even de tijd om de voorbeeld-app te controleren en ervoor te zorgen dat deze wordt uitgevoerd voordat u doorgaat. De voorbeeld-app is een elementaire ondersteuningsticket-API en bevat de volgende projecten:

  • SupportTicketApi.Api: het ASP.NET Core project dat als host fungeert voor de API.
  • SupportTicketApi.Data: bevat de EF Core contexten en modellen.
  • SupportTicketApi.AppHost: bevat de .NET.NET Aspire app-host en -configuratie.
  • SupportTicketApi.ServiceDefaults: bevat de standaardserviceconfiguraties.

Voer de app uit om ervoor te zorgen dat deze werkt zoals verwacht. Selecteer in het .NET.NET Aspire-dashboard het https Swagger-eindpunt en test het GET /api/SupportTickets-eindpunt van de API door de bewerking uit te vouwen en uitproberente selecteren. Selecteer uitvoeren om de aanvraag te verzenden en het antwoord weer te geven:

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

Migraties maken

Begin met het maken van enkele migraties die u wilt toepassen.

  1. Een terminal openen (Ctrl-+` in Visual Studio).

  2. Stel SupportTicketApiSupportTicketApi.Api in als de huidige map.

  3. Gebruik het dotnet ef opdrachtregelprogramma om een nieuwe migratie te maken om de initiële status van het databaseschema vast te leggen:

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

    De volgende opdracht:

    • Voert migratie-opdrachtregelprogramma EF Core uit in de map SupportTicketApi.Api. dotnet ef wordt uitgevoerd op deze locatie omdat de API-service de db-context gebruikt.
    • Hiermee maakt u een migratie met de naam InitialCreate.
    • Hiermee maakt u de migratie in de map Migrations in het project SupportTicketApi.Data.
  4. Wijzig het model zodat het een nieuwe eigenschap bevat. Open SupportTicketApi.DataModelsSupportTicket.cs en voeg een nieuwe eigenschap toe aan de klasse 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. Maak nog een nieuwe migratie om de wijzigingen in het model vast te leggen:

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

U hebt nu enkele migraties die moeten worden toegepast. Vervolgens maakt u een migratieservice die deze migraties toepast tijdens het opstarten van de app.

De migratieservice maken

Als u de migraties bij het opstarten wilt uitvoeren, moet u een service maken die de migraties toepast.

  1. Voeg een nieuw Worker Service project toe aan de oplossing. Als u Visual Studiogebruikt, klikt u met de rechtermuisknop op de oplossing in Solution Explorer en selecteert u Add>New Project. Selecteer Worker Service en geef het project een naam SupportTicketApi.MigrationService. Als u de opdrachtregel gebruikt, gebruikt u de volgende opdrachten uit de oplossingsmap:

    dotnet new worker -n SupportTicketApi.MigrationService
    dotnet sln add SupportTicketApi.MigrationService
    
  2. Voeg de SupportTicketApi.Data en SupportTicketApi.ServiceDefaults projectverwijzingen toe aan het SupportTicketApi.MigrationService project met behulp van Visual Studio of de opdrachtregel:

    dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.Data
    dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.ServiceDefaults
    
  3. Voeg de 📦Aspiretoe. Microsoft.EntityFrameworkCore.SqlServer NuGet-pakketreferentie naar het SupportTicketApi.MigrationService project met behulp van Visual Studio of de opdrachtregel:

    dotnet add package Aspire.Microsoft.EntityFrameworkCore.SqlServer
    
  4. Voeg de gemarkeerde regels toe aan het Program.cs-bestand in het SupportTicketApi.MigrationService project:

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

    In de voorgaande code:

  5. Vervang de inhoud van het Worker.cs-bestand in het SupportTicketApi.MigrationService project door de volgende code:

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

    In de voorgaande code:

    • De methode ExecuteAsync wordt aangeroepen wanneer de werker wordt gestart. Op zijn beurt worden de volgende stappen uitgevoerd:
      1. Hiermee haalt u een referentie op naar de TicketContext-service van deze serviceprovider.
      2. Roept EnsureDatabaseAsync aan om de database te maken als deze niet bestaat.
      3. Roept RunMigrationAsync aan om alle openstaande migraties toe te passen.
      4. Roept SeedDataAsync aan om de database te seeden met initiële gegevens.
      5. Stopt de werknemer met StopApplication.
    • De EnsureDatabaseAsync, RunMigrationAsyncen SeedDataAsync methoden bevatten allemaal hun respectieve databasebewerkingen met behulp van uitvoeringsstrategieën voor het afhandelen van tijdelijke fouten die kunnen optreden bij interactie met de database. Zie Verbindingstolerantievoor meer informatie over uitvoeringsstrategieën.

De migratieservice toevoegen aan de orchestrator

De migratieservice wordt gemaakt, maar moet worden toegevoegd aan de .NET.NET Aspire app-host, zodat deze wordt uitgevoerd wanneer de app wordt gestart.

  1. Open in het SupportTicketApi.AppHost project het bestand Program.cs.

  2. Voeg de volgende gemarkeerde code toe aan de methode 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();
    

    Hiermee wordt het SupportTicketApi.MigrationService project als een service in de .NET.NET Aspire app-host opgenomen.

    Belangrijk

    Als u Visual Studiogebruikt en u de optie Enlist in Aspire orchestration hebt geselecteerd bij het maken van het Worker Service project, wordt soortgelijke code automatisch toegevoegd met de servicenaam supportticketapi-migrationservice. Vervang die code door de voorgaande code.

Bestaande seeding-code verwijderen

Aangezien de migratieservice de database zaait, moet u de bestaande gegevenszaaicode uit het API-project verwijderen.

  1. Open in het SupportTicketApi.Api project het bestand Program.cs.

  2. Verwijder de gemarkeerde regels.

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

De migratieservice testen

Nu de migratieservice is geconfigureerd, voert u de app uit om de migraties te testen.

  1. Voer de app uit en bekijk het SupportTicketApi dashboard.

  2. Na een korte wachttijd wordt de status van de migrations-service weergegeven Voltooid.

    Een schermopname van het .NET.NET Aspire-dashboard met de migratieservice met de status Voltooid.

  3. Selecteer de View koppeling in de migratieservice om de logboeken te onderzoeken met de SQL-opdrachten die zijn uitgevoerd.

De code ophalen

U vindt de voltooide voorbeeld-app op GitHub.

Meer voorbeeldcode

De Aspire Shop voorbeeld-app gebruikt deze benadering om migraties toe te passen. Zie het AspireShop.CatalogDbManager project voor de implementatie van de migratieservice.