Applicare migrazioni Entity Framework Core in .NET Aspire
Poiché i progetti .NET.NET Aspire utilizzano un'architettura containerizzata, i database sono temporanei e possono essere ricreati in qualsiasi momento. Entity Framework Core (EF Core) usa una funzionalità denominata migrazioni per creare e aggiornare gli schemi di database. Poiché i database vengono ricreati all'avvio dell'app, è necessario applicare le migrazioni per inizializzare lo schema del database ogni volta che viene avviata l'app. Questa operazione viene eseguita registrando un progetto del servizio di migrazione nell'app che esegue le migrazioni durante l'avvio.
In questo tutorial, impari come configurare i progetti .NET Aspire per eseguire le migrazioni EF Core quando l'app si avvia.
Prerequisiti
Per usare .NET.NET Aspire, è necessario che il codice seguente sia installato in locale:
- .NET 8.0 o .NET 9.0
- Un runtime per contenitori conforme allo standard OCI, come:
- Docker Desktop o Podman. Per ulteriori informazioni, vedere ambiente di runtime del container.
- Un ambiente di sviluppo integrato (IDE) o un editor di codice, ad esempio:
- Visual Studio 2022 versione 17.9 o successiva (facoltativo)
-
Visual Studio Code (facoltativo)
- C# Dev Kit: Estensione (facoltativo)
- JetBrains Rider con .NET.NET Aspire plug-in (Facoltativo)
Per altre informazioni, vedere .NET.NET Aspire configurazione e strumentie .NET.NET Aspire SDK.
Scaricare l'app di avvio
Questa esercitazione usa un'app di esempio che illustra come applicare EF Core migrazioni in .NET Aspire. Usare Visual Studio per clonare 'app di esempio da GitHub o usare il comando seguente:
git clone https://github.com/MicrosoftDocs/aspire-docs-samples/
L'app di esempio si trova nella cartella SupportTicketApi. Apri la soluzione in Visual Studio o VS Code, prendi un momento per esaminare l'app di esempio e assicurati che funzioni prima di procedere. L'app di esempio è un'API del ticket di supporto rudimentale e contiene i progetti seguenti:
- SupportTicketApi.Api: Il progetto ASP.NET Core che ospita l'API.
- SupportTicketApi.Data: contiene i contesti e i modelli di EF Core.
- SupportTicketApi.AppHost: contiene l'host dell'applicazione e la configurazione .NET.NET Aspire.
- SupportTicketApi.ServiceDefaults: contiene le configurazioni predefinite del servizio.
Eseguire l'app per assicurarsi che funzioni come previsto. Nel dashboard di .NET.NET Aspire, selezionare l'endpoint https Swagger e testare l'endpoint dell'API GET /api/SupportTickets espandendo l'operazione e selezionando Esegui una prova. Selezionare Esegui per inviare la richiesta e visualizzare la risposta:
[
{
"id": 1,
"title": "Initial Ticket",
"description": "Test ticket, please ignore."
}
]
Creare migrazioni
Inizia creando alcune migrazioni da applicare.
Aprire un terminale (ctrl+` in Visual Studio).
Impostare SupportTicketApiSupportTicketApi.Api come directory corrente.
Usare lo strumento da riga di comando
dotnet ef
per creare una nuova migrazione per acquisire lo stato iniziale dello schema del database:dotnet ef migrations add InitialCreate --project ..\SupportTicketApi.Data\SupportTicketApi.Data.csproj
Il comando seguente:
- Esegue lo strumento da riga di comando per la migrazione
nella directory SupportTicketApi.Api. dotnet ef
viene eseguito in questa posizione perché il servizio API è dove viene usato il contesto del database. - Crea una migrazione denominata InitialCreate.
- Crea la migrazione nella cartella Migrations nel progetto SupportTicketApi.Data.
- Esegue lo strumento da riga di comando per la migrazione
Modifica il modello in modo che includa una nuova proprietà. Aprire SupportTicketApi.DataModelsSupportTicket.cs e aggiungere una nuova proprietà alla classe
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; } }
Creare un'altra nuova migrazione per acquisire le modifiche apportate al modello:
dotnet ef migrations add AddCompleted --project ..\SupportTicketApi.Data\SupportTicketApi.Data.csproj
Adesso hai alcune migrazioni da applicare. Si creerà quindi un servizio di migrazione che applica queste migrazioni durante l'avvio dell'app.
Creare il servizio di migrazione
Per eseguire le migrazioni all'avvio, è necessario creare un servizio che applica le migrazioni.
Aggiungere un nuovo progetto Worker Service alla soluzione. Se si usa Visual Studio, fare clic con il pulsante destro del mouse sulla soluzione in Esplora soluzioni e selezionare Add>New Project. Selezionare Worker Service e assegnare al progetto il nome SupportTicketApi.MigrationService. Se si usa la riga di comando, utilizzare i seguenti comandi dalla cartella della soluzione.
dotnet new worker -n SupportTicketApi.MigrationService dotnet sln add SupportTicketApi.MigrationService
Aggiungere i riferimenti al progetto SupportTicketApi.Data e SupportTicketApi.ServiceDefaults al progetto di SupportTicketApi.MigrationService usando Visual Studio o la riga di comando:
dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.Data dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.ServiceDefaults
Aggiungere il 📦Aspire.Microsoft.EntityFrameworkCore.SqlServer come riferimento al pacchetto NuGet nel progetto SupportTicketApi.MigrationService utilizzando Visual Studio o la riga di comando:
dotnet add package Aspire.Microsoft.EntityFrameworkCore.SqlServer
Aggiungere le righe evidenziate al file Program.cs nel progetto 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();
Nel codice precedente:
- Il metodo di estensione
AddServiceDefaults
aggiunge funzionalità predefinite del servizio. - Il metodo di estensione
AddOpenTelemetry
configura la funzionalità OpenTelemetry. - Il metodo di estensione
AddSqlServerDbContext
aggiunge il servizioTicketContext
alla raccolta di servizi. Questo servizio viene usato per eseguire le migrazioni e inizializzare il database.
- Il metodo di estensione
Sostituire il contenuto del file di Worker.cs nel progetto SupportTicketApi.MigrationService con il codice seguente:
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); }); } }
Nel codice precedente:
- Il metodo
ExecuteAsync
viene chiamato quando il lavoratore inizia. A sua volta esegue i passaggi seguenti:- Ottiene un riferimento al servizio
TicketContext
dal fornitore di servizi. - Chiama
EnsureDatabaseAsync
per creare il database, se questo non esiste. - Chiama
RunMigrationAsync
per eseguire eventuali migrazioni in sospeso. - Chiama
SeedDataAsync
per eseguire il seeding del database con i dati iniziali. - Arresta il lavoratore con
StopApplication
.
- Ottiene un riferimento al servizio
- I metodi
EnsureDatabaseAsync
,RunMigrationAsync
eSeedDataAsync
incapsulano tutte le rispettive operazioni di database usando strategie di esecuzione per gestire gli errori temporanei che possono verificarsi durante l'interazione con il database. Per ulteriori informazioni sulle strategie di esecuzione, consulta la sezione Resilienza delle connessioni.
- Il metodo
Aggiungere il servizio di migrazione all'orchestratore
Il servizio di migrazione viene creato, ma deve essere aggiunto all'host dell'app .NET.NET Aspire in modo che venga eseguito all'avvio dell'app.
Nel progetto SupportTicketApi.AppHost, apri il file Program.cs.
Aggiungere il codice evidenziato seguente al metodo
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();
In questo modo, il progetto SupportTicketApi.MigrationService viene elencato come servizio nell'host dell'app .NET.NET Aspire.
Importante
Se si usa Visual Studioe si è selezionata l'opzione Enlist in Aspire orchestration durante la creazione del progetto di Worker Service, il codice simile viene aggiunto automaticamente con il nome del servizio
supportticketapi-migrationservice
. Sostituisci quel codice con il codice precedente.
Rimuovere il codice di seeding esistente
Poiché il servizio di migrazione esegue il seeding del database, è necessario rimuovere il codice di seeding dei dati esistente dal progetto API.
Nel progetto SupportTicketApi.Api, apri il file Program.cs.
Eliminare le righe evidenziate.
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(); } } }
Testare il servizio di migrazione
Ora che il servizio di migrazione è configurato, eseguire l'app per testare le migrazioni.
Avvia l'app e osserva il dashboard SupportTicketApi.
Dopo un breve periodo di attesa, lo stato del servizio
migrations
mostrerà Completato.Selezionare il collegamento View nel servizio di migrazione per esaminare i log che mostrano i comandi SQL eseguiti.
Ottenere il codice
Puoi trovare l'applicazione di esempio completata su GitHub.
Altro codice di esempio
L'app di esempio Aspire Shop usa questo approccio per applicare le migrazioni. Consultare il progetto AspireShop.CatalogDbManager
per l'implementazione del servizio di migrazione.