Freigeben über


Anwenden der Entity Framework Core-Migrationen in .NET Aspire

Da .NET.NET Aspire Projekte eine containerisierte Architektur verwenden, sind Datenbanken kurzlebig und können jederzeit neu erstellt werden. Entity Framework Core (EF Core) verwendet ein Feature namens Migrationen zum Erstellen und Aktualisieren von Datenbankschemas. Da Datenbanken beim Starten der App neu erstellt werden, müssen Sie Migrationen anwenden, um das Datenbankschema bei jedem Start der App zu initialisieren. Dies erfolgt durch Registrieren eines Migrationsdienstprojekts in Ihrer App, das Migrationen während des Starts ausführt.

In diesem Lernprogramm erfahren Sie, wie Sie .NET Aspire Projekte so konfigurieren, dass EF Core Migrationen während des App-Starts ausgeführt werden.

Voraussetzungen

Um mit .NET.NET Aspirezu arbeiten, benötigen Sie folgendes lokal installiert:

Weitere Informationen finden Sie unter .NET.NET Aspire Setup und Toolsund .NET.NET Aspire SDK.

Startanwendung herunterladen

In diesem Lernprogramm wird eine Beispiel-App verwendet, die veranschaulicht, wie EF Core Migrationen in .NET Aspireangewendet werden. Verwenden Sie Visual Studio, um die Beispiel-App von GitHub zu klonen oder den folgenden Befehl zu verwenden:

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

Die Beispiel-App befindet sich im Ordner SupportTicketApi. Öffnen Sie die Lösung in Visual Studio oder VS Code, und nehmen Sie sich einen Moment Zeit, um die Beispiel-App zu überprüfen und sicherzustellen, dass sie ausgeführt wird, bevor Sie fortfahren. Die Beispiel-App ist eine rudimentäre Support-Ticket-API, und enthält die folgenden Projekte:

  • SupportTicketApi.Api: Das ASP.NET Core Projekt, das die API hostet.
  • SupportTicketApi.Data: Enthält die EF Core Kontexte und Modelle.
  • SupportTicketApi.AppHost: Enthält den .NET.NET Aspire App-Host und die Konfiguration.
  • SupportTicketApi.ServiceDefaults: Enthält die Standarddienstkonfigurationen.

Führen Sie die App aus, um sicherzustellen, dass sie erwartungsgemäß funktioniert. Wählen Sie im .NET.NET Aspire-Dashboard den https Swagger-Endpunkt aus, und testen Sie die GET /api/SupportTickets Endpunkt der API, indem Sie den Vorgang erweitern und Ausprobierenauswählen. Wählen Sie Ausführen aus, um die Anforderung zu senden und die Antwort anzuzeigen:

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

Migrationen erstellen

Beginnen Sie, indem Sie einige Migrationen erstellen, die angewendet werden sollen.

  1. Öffnen Sie ein Terminal (STRG+` in Visual Studio).

  2. Legen Sie SupportTicketApiSupportTicketApi.Api als aktuelles Verzeichnis fest.

  3. Verwenden Sie das dotnet ef Befehlszeilentool, um eine neue Migration zu erstellen, um den Anfangszustand des Datenbankschemas zu erfassen:

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

    Der Befehl "Fortfahren":

    • Führt das Befehlszeilentool für die Migration EF Core im Verzeichnis SupportTicketApi.Api aus. dotnet ef wird an diesem Speicherort ausgeführt, da der API-Dienst den DB-Kontext verwendet.
    • Erstellt eine Migration mit dem Namen InitialCreate.
    • Erstellt die Migration im Migrations- Ordner des Projekts SupportTicketApi.Data.
  4. Ändern Sie das Modell so, dass es eine neue Eigenschaft enthält. Öffnen Sie SupportTicketApi.DataModelsSupportTicket.cs, und fügen Sie der klasse SupportTicket eine neue Eigenschaft hinzu:

    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. Erstellen Sie eine weitere neue Migration, um die Änderungen am Modell zu erfassen:

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

Jetzt müssen Sie einige Migrationen anwenden. Als Nächstes erstellen Sie einen Migrationsdienst, der diese Migrationen während des App-Starts anwendet.

Erstellen Sie den Migrationsdienst

Um die Migrationen beim Start auszuführen, müssen Sie einen Dienst erstellen, der die Migrationen anwendet.

  1. Fügen Sie der Projektmappe ein neues Worker Service-Projekt hinzu. Wenn Sie Visual Studioverwenden, klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf die Projektmappe, und wählen Sie Add>New Projectaus. Wählen Sie Worker Service aus, und benennen Sie das Projekt SupportTicketApi.MigrationService. Wenn Sie die Befehlszeile verwenden, verwenden Sie die folgenden Befehle aus dem Lösungsverzeichnis:

    dotnet new worker -n SupportTicketApi.MigrationService
    dotnet sln add SupportTicketApi.MigrationService
    
  2. Fügen Sie zum SupportTicketApi.Data-Projekt die SupportTicketApi.ServiceDefaults- und SupportTicketApi.MigrationService-Projektverweise mithilfe von Visual Studio oder der Befehlszeile hinzu.

    dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.Data
    dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.ServiceDefaults
    
  3. Fügen Sie die 📦Aspire.Microsoft.EntityFrameworkCore.SqlServer NuGet-Paketreferenz zum Projekt SupportTicketApi.MigrationService mithilfe von Visual Studio oder über die Befehlszeile hinzu.

    dotnet add package Aspire.Microsoft.EntityFrameworkCore.SqlServer
    
  4. Fügen Sie der Program.cs-Datei im SupportTicketApi.MigrationService Projekt die hervorgehobenen Zeilen hinzu:

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

    Im vorherigen Code:

    • Mit der Erweiterungsmethode wird die Dienststandardfunktionenzu hinzugefügt.
    • Die AddOpenTelemetry Erweiterungsmethode konfiguriert OpenTelemetry Funktionalität.
    • Die AddSqlServerDbContext-Erweiterungsmethode fügt der Dienstkollektion den TicketContext-Dienst hinzu. Dieser Dienst wird zum Ausführen von Migrationen und zum Seeden der Datenbank verwendet.
  5. Ersetzen Sie den Inhalt der datei Worker.cs im SupportTicketApi.MigrationService Projekt durch den folgenden 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);
            });
        }
    }
    

    Im vorherigen Code:

    • Die ExecuteAsync-Methode wird aufgerufen, wenn der Worker startet. Sie führt wiederum die folgenden Schritte aus:
      1. Ruft eine Referenz auf den TicketContext-Dienst vom Dienstanbieter ab.
      2. Ruft EnsureDatabaseAsync auf, um die Datenbank zu erstellen, wenn sie nicht vorhanden ist.
      3. Ruft RunMigrationAsync auf, um ausstehende Migrationen anzuwenden.
      4. Ruft SeedDataAsync auf, um die Datenbank mit Anfangsdaten zu versehen.
      5. Beendet den Worker mit StopApplication.
    • Die Methoden EnsureDatabaseAsync, RunMigrationAsyncund SeedDataAsync kapseln alle ihre jeweiligen Datenbankvorgänge mithilfe von Ausführungsstrategien, um vorübergehende Fehler zu behandeln, die bei der Interaktion mit der Datenbank auftreten können. Weitere Informationen zu Durchführungsstrategien finden Sie unter Verbindungszuverlässigkeit.

Hinzufügen des Migrationsdiensts zum Orchestrator

Der Migrationsdienst wird erstellt, muss jedoch dem .NET.NET Aspire App-Host hinzugefügt werden, damit er beim Starten der App ausgeführt wird.

  1. Öffnen Sie im SupportTicketApi.AppHost Projekt die Program.cs Datei.

  2. Fügen Sie der ConfigureServices-Methode den folgenden hervorgehobenen Code hinzu:

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

    Dadurch wird das SupportTicketApi.MigrationService-Projekt als Dienst im .NET.NET Aspire App-Host aufgelistet.

    Wichtig

    Wenn Sie Visual Studioverwenden und beim Erstellen des Projekts Enlist in Aspire orchestration die Option Worker Service ausgewählt haben, wird automatisch ähnlicher Code mit dem Dienstnamen supportticketapi-migrationservicehinzugefügt. Ersetzen Sie diesen Code durch den vorherigen Code.

Vorhandenen Seedingcode entfernen

Da der Migrationsdienst die Datenbank besät, sollten Sie den vorhandenen Datensatzungscode aus dem API-Projekt entfernen.

  1. Öffnen Sie im SupportTicketApi.Api Projekt die Program.cs Datei.

  2. Löschen Sie die hervorgehobenen Zeilen.

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

Test des Migrationsdiensts

Nachdem der Migrationsdienst konfiguriert ist, führen Sie die App aus, um die Migrationen zu testen.

  1. Führen Sie die App aus, und beobachten Sie das SupportTicketApi-Dashboard.

  2. Nach einer kurzen Wartezeit wird der migrations Dienststatus Abgeschlossenangezeigt.

    Screenshot des .NET-.NET Aspire-Dashboards mit dem Migrationsdienst im abgeschlossenen Zustand.

  3. Wählen Sie den Link View des Migrationsdiensts aus, um die Protokolle zu untersuchen, die die ausgeführten SQL-Befehle enthalten.

Code abrufen

Sie finden die abgeschlossene Beispiel-App auf GitHub.

Weitere Beispielcode

Die Aspire Shop Beispiel-App verwendet diesen Ansatz, um Migrationen anzuwenden. Informationen zur Implementierung des Migrationsdiensts finden Sie im AspireShop.CatalogDbManager Projekt.