Aplicar migraciones de Entity Framework Core en .NET Aspire
Dado que .NET.NET Aspire proyectos usan una arquitectura en contenedores, las bases de datos son efímeras y se pueden volver a crear en cualquier momento. Entity Framework Core (EF Core) usa una característica denominada migraciones para crear y actualizar esquemas de base de datos. Dado que las bases de datos se vuelven a crear cuando se inicia la aplicación, debe aplicar migraciones para inicializar el esquema de la base de datos cada vez que se inicia la aplicación. Esto se logra registrando un proyecto de servicio de migración en la aplicación que ejecuta migraciones durante el inicio.
En este tutorial, aprenderá a configurar .NET Aspire proyectos para ejecutar migraciones EF Core durante el inicio de la aplicación.
Prerrequisitos
Para trabajar con .NET.NET Aspire, necesita lo siguiente instalado localmente:
- .NET 8.0 o .NET 9.0
- Un entorno de ejecución de contenedor compatible con OCI, como:
- Docker Escritorio o Podman. Para obtener más información, consulte container runtime.
- Un entorno para desarrolladores integrado (IDE) o un editor de código, como:
- Visual Studio 2022 versión 17.9 o posterior (opcional)
-
Visual Studio Code (opcional)
- C# Dev Kit: extensión (opcional)
- JetBrains Rider con .NET.NET Aspire plugin (opcional)
Para obtener más información, consulte configuración y herramientas de .NET.NET Aspirey sdk de .NET.NET Aspire.
Obtención de la aplicación de inicio
En este tutorial se usa una aplicación de ejemplo que muestra cómo aplicar migraciones de EF Core en .NET Aspire. Use Visual Studio para clonar la aplicación de ejemplo desde GitHub o use el comando siguiente:
git clone https://github.com/MicrosoftDocs/aspire-docs-samples/
La aplicación de ejemplo se encuentra en la carpeta SupportTicketApi. Abra la solución en Visual Studio o VS Code y dedique un momento a revisar la aplicación de ejemplo y asegúrese de que se ejecuta antes de continuar. La aplicación de ejemplo es una API de incidencias de soporte técnico rudimentaria y contiene los siguientes proyectos:
- SupportTicketApi.Api: el proyecto ASP.NET Core que alberga la API.
- SupportTicketApi.Data: contiene los contextos y modelos de EF Core.
- SupportTicketApi.AppHost: contiene el host de la aplicación y la configuración de .NET.NET Aspire.
- supportTicketApi.ServiceDefaults: contiene las configuraciones de servicio predeterminadas.
Ejecute la aplicación para asegurarse de que funciona según lo previsto. En el panel de .NET.NET Aspire, seleccione la https punto de conexión de Swagger y pruebe el punto de conexión GET /api/SupportTickets; para ello, expanda la operación y seleccione Pruébelo. Seleccione Ejecutar para enviar la solicitud y ver la respuesta:
[
{
"id": 1,
"title": "Initial Ticket",
"description": "Test ticket, please ignore."
}
]
Crear migraciones
Empiece por crear algunas migraciones para aplicar.
Abra un terminal (Ctrl+` en Visual Studio).
Establezca SupportTicketApiSupportTicketApi.Api como directorio actual.
Use la herramienta de línea de comandos
dotnet ef
para crear una nueva migración para capturar el estado inicial del esquema de la base de datos:dotnet ef migrations add InitialCreate --project ..\SupportTicketApi.Data\SupportTicketApi.Data.csproj
Comando de procedimiento:
- En el directorio
SupportTicketApi.Api, ejecuta la herramienta de línea de comandos de migración . dotnet ef
se ejecuta en esta ubicación porque el servicio de API es donde se usa el contexto de base de datos. - Crea una migración denominada InitialCreate.
- Crea la migración en la carpeta Migrations del proyecto SupportTicketApi.Data.
- En el directorio
Modifique el modelo para que incluya una nueva propiedad. Abra SupportTicketApi.DataModelsSupportTicket.cs y agregue una nueva propiedad a la clase
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; } }
Cree otra nueva migración para capturar los cambios en el modelo:
dotnet ef migrations add AddCompleted --project ..\SupportTicketApi.Data\SupportTicketApi.Data.csproj
Ahora tienes que aplicar algunas migraciones. A continuación, creará un servicio de migración que aplica estas migraciones durante el inicio de la aplicación.
Creación del servicio de migración
Para ejecutar las migraciones al inicio, debe crear un servicio que aplique las migraciones.
Agregue un nuevo proyecto de Worker Service a la solución. Si usa Visual Studio, haga clic con el botón derecho en la solución en el Explorador de soluciones y seleccione Add>New Project. Seleccione Worker Service y asigne al proyecto el nombre SupportTicketApi.MigrationService. Si usa la línea de comandos, use los siguientes comandos desde el directorio de la solución:
dotnet new worker -n SupportTicketApi.MigrationService dotnet sln add SupportTicketApi.MigrationService
Agregue las referencias del proyecto SupportTicketApi.Data y SupportTicketApi.ServiceDefaults al proyecto de SupportTicketApi.MigrationService mediante Visual Studio o la línea de comandos:
dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.Data dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.ServiceDefaults
Agregue el 📦Aspire. Referencia del paquete NuGet Microsoft.EntityFrameworkCore.SqlServer al proyecto de SupportTicketApi.MigrationService mediante Visual Studio o la línea de comandos:
dotnet add package Aspire.Microsoft.EntityFrameworkCore.SqlServer
Agregue las líneas resaltadas al archivo Program.cs en el proyecto de 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();
En el código anterior:
- El método de extensión
AddServiceDefaults
agrega la funcionalidad predeterminada del servicio. - El método de extensión
AddOpenTelemetry
configura OpenTelemetry funcionalidad. - El método de extensión
AddSqlServerDbContext
agrega el servicioTicketContext
a la colección de servicios. Este servicio se usa para ejecutar migraciones y inicializar la base de datos.
- El método de extensión
Reemplace el contenido del archivo Worker.cs en el proyecto de SupportTicketApi.MigrationService por el código siguiente:
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); }); } }
En el código anterior:
- Se llama al método
ExecuteAsync
cuando se inicia el trabajo. A su vez, realiza los pasos siguientes:- Obtiene una referencia al servicio
TicketContext
del proveedor de servicios. - Llama a
EnsureDatabaseAsync
para crear la base de datos si no existe. - Llama a
RunMigrationAsync
para aplicar cualquier migración pendiente. - Llama a
SeedDataAsync
para inicializar la base de datos con datos iniciales. - Detiene al trabajador con
StopApplication
.
- Obtiene una referencia al servicio
- Los métodos
EnsureDatabaseAsync
,RunMigrationAsync
ySeedDataAsync
encapsulan sus respectivas operaciones de base de datos mediante estrategias de ejecución para controlar errores transitorios que pueden producirse al interactuar con la base de datos. Para obtener más información sobre las estrategias de ejecución, consulte resistencia de conexión.
- Se llama al método
Añadir el servicio de migración al orquestador
Se crea el servicio de migración, pero debe agregarse al host de la aplicación .NET.NET Aspire para que se ejecute cuando se inicie la aplicación.
En el proyecto SupportTicketApi.AppHost, abra el archivo Program.cs.
Agregue el código resaltado siguiente al método
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();
Esto inscribe el proyecto SupportTicketApi.MigrationService como servicio en la aplicación host .NET.NET Aspire.
Importante
Si usa Visual Studioy seleccionó la opción Enlist in Aspire orchestration al crear el proyecto de Worker Service, se agrega código similar automáticamente con el nombre del servicio
supportticketapi-migrationservice
. Reemplace ese código por el código anterior.
Eliminación del código de propagación existente
Dado que el servicio de migración inicializa la base de datos, debe quitar el código de inicialización de datos existente del proyecto de API.
En el proyecto SupportTicketApi.Api, abra el archivo Program.cs.
Elimine las líneas resaltadas.
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(); } } }
Prueba del servicio de migración
Ahora que el servicio de migración está configurado, ejecute la aplicación para probar las migraciones.
Ejecute la aplicación y observe el panel de SupportTicketApi.
Después de una breve espera, el estado del servicio
migrations
mostrará Finalizado.Seleccione el vínculo View en el servicio de migración para investigar los registros que muestran los comandos SQL que se ejecutaron.
Obtención del código
Puede encontrar la aplicación de ejemplo completada en GitHub.
Más código de ejemplo
La aplicación de ejemplo Aspire Shop usa este enfoque para aplicar migraciones. Consulte el proyecto de AspireShop.CatalogDbManager
para la implementación del servicio de migración.