Appliquer des migrations de Entity Framework Core dans .NET Aspire
Étant donné que .NET.NET Aspire projets utilisent une architecture conteneurisée, les bases de données sont éphémères et peuvent être recréées à tout moment. Entity Framework Core (EF Core) utilise une fonctionnalité appelée migrations pour créer et mettre à jour des schémas de base de données. Étant donné que les bases de données sont recréées au démarrage de l’application, vous devez appliquer des migrations pour initialiser le schéma de base de données chaque fois que votre application démarre. Pour ce faire, inscrivez un projet de service de migration dans votre application qui exécute des migrations au démarrage.
Dans ce tutoriel, vous allez apprendre à configurer les projets .NET Aspire pour exécuter des migrations EF Core lors du démarrage de l'application.
Conditions préalables
Pour utiliser .NET.NET Aspire, vous avez besoin de l’installation locale suivante :
- .NET 8.0 ou .NET 9.0
- Un runtime de conteneur conforme à OCI, tel que :
- Docker Desktop ou Podman. Pour plus d’informations, consultez le runtime de conteneur .
- Un environnement de développement intégré (IDE) ou un éditeur de code, par exemple :
- Visual Studio 2022 version 17.9 ou ultérieure (facultatif)
-
Visual Studio Code (facultatif)
- Extension C# Dev Kit: (facultatif)
- JetBrains Rider avec .NET.NET Aspire plug-in (facultatif)
Pour plus d’informations, consultez .NETla configuration et les outils.NET Aspire, et .NETle SDK.NET Aspire.
Obtenir l’application de démarrage
Ce tutoriel utilise un exemple d’application qui montre comment appliquer des migrations EF Core dans .NET Aspire. Utilisez Visual Studio pour cloner l’exemple d’application à partir de GitHub ou utilisez la commande suivante :
git clone https://github.com/MicrosoftDocs/aspire-docs-samples/
L’exemple d’application se trouve dans le dossier SupportTicketApi. Ouvrez la solution dans Visual Studio ou VS Code et prenez un moment pour passer en revue l’exemple d’application et assurez-vous qu’elle s’exécute avant de continuer. L’exemple d’application est une API de ticket de support rudimentaire et contient les projets suivants :
- SupportTicketApi.Api: projet ASP.NET Core qui héberge l’API.
- SupportTicketApi.Data: contient les contextes et modèles EF Core.
- SupportTicketApi.AppHost: contient l’hôte et la configuration de l’application .NET.NET Aspire.
- SupportTicketApi.ServiceDefaults: contient les configurations de service par défaut.
Exécutez l’application pour vous assurer qu’elle fonctionne comme prévu. Dans le tableau de bord
[
{
"id": 1,
"title": "Initial Ticket",
"description": "Test ticket, please ignore."
}
]
Créer des migrations
Commencez par créer des migrations à appliquer.
Ouvrez un terminal (Ctrl+` dans Visual Studio).
Définissez SupportTicketApiSupportTicketApi.Api comme répertoire actif.
Utilisez l’outil en ligne de commande
dotnet ef
pour créer une migration pour capturer l’état initial du schéma de base de données :dotnet ef migrations add InitialCreate --project ..\SupportTicketApi.Data\SupportTicketApi.Data.csproj
La commande suivante :
- Exécute l'outil de ligne de commande de migration EF Core dans le répertoire SupportTicketApi.Api.
dotnet ef
est exécuté à cet emplacement, car le service d’API est l’emplacement où le contexte de base de données est utilisé. - Crée une migration nommée InitialCreate.
- Crée la migration dans le dossier Migrations dans le projet SupportTicketApi.Data.
- Exécute l'outil de ligne de commande de migration EF Core dans le répertoire SupportTicketApi.Api.
Modifiez le modèle afin qu’il inclut une nouvelle propriété. Ouvrez SupportTicketApi.DataModelsSupportTicket.cs et ajoutez une nouvelle propriété à la 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; } }
Créez une autre migration pour capturer les modifications apportées au modèle :
dotnet ef migrations add AddCompleted --project ..\SupportTicketApi.Data\SupportTicketApi.Data.csproj
Vous avez maintenant des migrations à appliquer. Ensuite, vous allez créer un service de migration qui applique ces migrations au démarrage de l’application.
Créer le service de migration
Pour exécuter les migrations au démarrage, vous devez créer un service qui applique les migrations.
Ajoutez un nouveau projet Worker Service à la solution. Si vous utilisez Visual Studio, cliquez avec le bouton droit sur la solution dans l’Explorateur de solutions et sélectionnez Add>New Project. Sélectionnez Worker Service et nommez le projet SupportTicketApi.MigrationService. Si vous utilisez la ligne de commande, utilisez les commandes suivantes à partir du répertoire de solution :
dotnet new worker -n SupportTicketApi.MigrationService dotnet sln add SupportTicketApi.MigrationService
Ajoutez les références de projet SupportTicketApi.Data et de SupportTicketApi.ServiceDefaults au projet SupportTicketApi.MigrationService à l’aide de Visual Studio ou de la ligne de commande :
dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.Data dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.ServiceDefaults
Ajoutez la 📦Aspire. Microsoft.EntityFrameworkCore.SqlServer référence de package NuGet au projet SupportTicketApi.MigrationService à l’aide de Visual Studio ou de la ligne de commande :
dotnet add package Aspire.Microsoft.EntityFrameworkCore.SqlServer
Ajoutez les lignes mises en surbrillance au fichier Program.cs dans le projet 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();
Dans le code précédent :
- La méthode d’extension
AddServiceDefaults
ajoute des fonctionnalités de service par défaut. - La méthode d’extension
AddOpenTelemetry
configure la fonctionnalité OpenTelemetry. - La méthode d’extension
AddSqlServerDbContext
ajoute le serviceTicketContext
à la collection de services. Ce service est utilisé pour exécuter des migrations et amorçage de la base de données.
- La méthode d’extension
Remplacez le contenu du fichier Worker.cs dans le projet SupportTicketApi.MigrationService par le code suivant :
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); }); } }
Dans le code précédent :
- La méthode
ExecuteAsync
est appelée au démarrage du worker. Elle effectue à son tour les étapes suivantes :- Obtient une référence au service
TicketContext
du fournisseur de services. - Appelle
EnsureDatabaseAsync
pour créer la base de données si elle n’existe pas. - Appelle
RunMigrationAsync
pour appliquer les migrations en attente. - Appelle
SeedDataAsync
pour amorçage de la base de données avec des données initiales. - Arrête le worker avec
StopApplication
.
- Obtient une référence au service
- Les méthodes
EnsureDatabaseAsync
,RunMigrationAsync
etSeedDataAsync
encapsulent toutes leurs opérations de base de données respectives à l’aide de stratégies d’exécution pour gérer les erreurs temporaires qui peuvent se produire lors de l’interaction avec la base de données. Pour en savoir plus sur les stratégies d’exécution, consultez Résilience de Connexion.
- La méthode
Ajouter le service de migration à l’orchestrateur
Le service de migration est créé, mais il doit être ajouté à l’hôte d’application .NET.NET Aspire afin qu’il s’exécute au démarrage de l’application.
Dans le projet SupportTicketApi.AppHost, ouvrez le fichier Program.cs.
Ajoutez le code en surbrillance suivant à la méthode
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();
Cela inscrit le projet SupportTicketApi.MigrationService en tant que service dans l’hôte d’application .NET.NET Aspire.
Important
Si vous utilisez Visual Studioet que vous avez sélectionné l’option Enlist in Aspire orchestration lors de la création du projet Worker Service, le code similaire est ajouté automatiquement avec le nom du service
supportticketapi-migrationservice
. Remplacez ce code par le code précédent.
Supprimer le code d’amorçage existant
Étant donné que le service de migration crée la base de données, vous devez supprimer le code d’amorçage de données existant du projet d’API.
Dans le projet SupportTicketApi.Api, ouvrez le fichier Program.cs.
Supprimez les lignes en surbrillance.
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(); } } }
Tester le service de migration
Maintenant que le service de migration est configuré, exécutez l’application pour tester les migrations.
Exécutez l’application et observez le tableau de bord SupportTicketApi.
Après une courte attente, l’état du service
migrations
s’affiche terminé.Sélectionnez le lien View sur le service de migration pour examiner les journaux montrant les commandes SQL exécutées.
Obtenir le code
Vous trouverez l’exemple d’application terminé sur GitHub.
Autres exemples de code
L’exemple d’application Aspire Shop utilise cette approche pour appliquer des migrations. Consultez le projet AspireShop.CatalogDbManager
pour l’implémentation du service de migration.