Partie 4, Razor Pages avec migrations EF Core dans ASP.NET Core

Par Tom Dykstra, Jon P Smith et Rick Anderson

L’application web Contoso University montre comment créer des applications web Razor Pages avec EF Core et Visual Studio. Pour obtenir des informations sur la série de didacticiels, consultez le premier didacticiel.

Si vous rencontrez des problèmes que vous ne pouvez pas résoudre, téléchargez l’application finale et comparez ce code à celui que vous avez créé en suivant le tutoriel.

Ce tutoriel présente la fonctionnalité de migrations EF Core pour gérer les modifications du modèle de données.

Quand une nouvelle application est développée, le modèle de données change fréquemment. Chaque fois que le modèle change, il est désynchronisé avec la base de données. Cette série de tutoriels a commencé par la configuration d’Entity Framework pour créer la base de données si elle n’existait pas. Chaque fois que le modèle de données change, la base de données doit être supprimée. À l’exécution suivante de l’application, l’appel à EnsureCreated a pour effet de recréer la base de données en fonction du nouveau modèle de données. La classe DbInitializer s’exécute ensuite pour amorcer la nouvelle base de données.

Cette approche pour conserver la synchronisation de la base de données avec le modèle de données fonctionne bien jusqu’à ce que l’application doive être déployée en production. Quand l’application s’exécute en production, elle stocke généralement des données qui doivent être tenues à jour. L’application ne peut pas commencer avec une base de données de test chaque fois qu’une modification est apportée (par exemple en cas d’ajout d’une nouvelle colonne). La fonctionnalité de migrations EF Core résout ce problème en permettant à EF Core de mettre à jour le schéma de base de données au lieu de créer une nouvelle base de données.

Au lieu de supprimer et de recréer la base de données quand le modèle de données change, les migrations mettent à jour le schéma et conservent les données existantes.

Remarque

Limitations de SQLite

Ce tutoriel utilise la fonctionnalité Migrations d’Entity Framework Core lorsque cela est possible. Les migrations mettent à jour le schéma de la base de données pour qu’elle corresponde aux modifications dans le modèle de données. Toutefois, les migrations effectuent uniquement les types de modifications qui sont pris en charge par le moteur de base de données. En outre, les fonctionnalités de modification du schéma SQLite sont limitées. Par exemple, l’ajout d’une colonne est pris en charge, mais pas sa suppression. Si vous créez une migration pour supprimer une colonne, la commande ef migrations add réussit mais la commande ef database update échoue.

Pour remédier aux limitations de SQLite, vous devez écrire le code de migrations manuellement pour regénérer le tableau lorsqu’un élément est modifié. Ce code se place dans les méthodes Up et Down pour une migration et implique les tâches suivantes :

  • La création d’un nouveau tableau.
  • La copie de données de l’ancien tableau vers le nouveau.
  • La suppression de l’ancien tableau.
  • Renommer la nouvelle table.

L’écriture d’un tel code propre à une base de données n’est pas abordée dans ce tutoriel. En effet, ce tutoriel supprime et recrée la base de données chaque fois qu’une tentative d’application d’une migration échoue. Pour plus d’informations, consultez les ressources suivantes :

Supprimer la base de données

Utilisez l’Explorateur d’objets SQL Server (SSOX) pour supprimer la base de données ou exécutez la commande suivante dans la console du Gestionnaire de package (PMC) :

Drop-Database

Créer une migration initiale

Exécutez les commandes suivantes dans PMC :

Add-Migration InitialCreate
Update-Database

Supprimer EnsureCreated

Cette série de tutoriels a commencé en utilisant EnsureCreated. La méthode EnsureCreated ne crée pas de table d’historique des migrations et ne peut donc pas être utilisée avec les migrations. Elle est destinée à effectuer des tests et un prototypage rapide, où la base de données est supprimée et recréée fréquemment.

À partir de là, les tutoriels utilisent des migrations.

Dans Program.cs, supprimez la ligne suivante :

context.Database.EnsureCreated();

Exécutez l’application et vérifiez que la base de données est amorcée.

Méthodes Up et Down

La commande EF Coremigrations add a généré du code pour créer la base de données. Ce code de migration se trouve dans le fichier Migrations\<timestamp>_InitialCreate.cs. La méthode Up de la classe InitialCreate crée les tables de base de données qui correspondent aux jeux d’entités du modèle de données. La méthode Down les supprime, comme indiqué dans l’exemple suivant :

using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;

namespace ContosoUniversity.Migrations
{
    public partial class InitialCreate : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Course",
                columns: table => new
                {
                    CourseID = table.Column<int>(nullable: false),
                    Title = table.Column<string>(nullable: true),
                    Credits = table.Column<int>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Course", x => x.CourseID);
                });

            migrationBuilder.CreateTable(
                name: "Student",
                columns: table => new
                {
                    ID = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    LastName = table.Column<string>(nullable: true),
                    FirstMidName = table.Column<string>(nullable: true),
                    EnrollmentDate = table.Column<DateTime>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Student", x => x.ID);
                });

            migrationBuilder.CreateTable(
                name: "Enrollment",
                columns: table => new
                {
                    EnrollmentID = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    CourseID = table.Column<int>(nullable: false),
                    StudentID = table.Column<int>(nullable: false),
                    Grade = table.Column<int>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Enrollment", x => x.EnrollmentID);
                    table.ForeignKey(
                        name: "FK_Enrollment_Course_CourseID",
                        column: x => x.CourseID,
                        principalTable: "Course",
                        principalColumn: "CourseID",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_Enrollment_Student_StudentID",
                        column: x => x.StudentID,
                        principalTable: "Student",
                        principalColumn: "ID",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateIndex(
                name: "IX_Enrollment_CourseID",
                table: "Enrollment",
                column: "CourseID");

            migrationBuilder.CreateIndex(
                name: "IX_Enrollment_StudentID",
                table: "Enrollment",
                column: "StudentID");
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "Enrollment");

            migrationBuilder.DropTable(
                name: "Course");

            migrationBuilder.DropTable(
                name: "Student");
        }
    }
}

Le code précédent concerne la migration initiale. Le code :

  • A été généré par la commande migrations add InitialCreate.
  • Est exécuté par la commande database update.
  • Crée une base de données pour le modèle de données spécifié par la classe du contexte de base de données.

Le paramètre de nom de migration (InitialCreate dans l’exemple) est utilisé comme nom de fichier. Le nom de la migration peut être n’importe quel nom de fichier valide. Nous vous conseillons néanmoins de choisir un mot ou une expression qui résume ce qui est effectué dans la migration. Par exemple, une migration ajoutant une table de département pourrait se nommer « TableAjoutDépartement ».

Table d’historique des migrations

  • Utilisez SSOX ou l’outil SQLite pour inspecter la base de données.
  • Notez l’ajout d’une table __EFMigrationsHistory. La table __EFMigrationsHistory effectue le suivi des migrations qui ont été appliquées à la base de données.
  • Examinez les données contenues dans la table __EFMigrationsHistory. Elle présente une ligne pour la première migration.

Capture instantanée du modèle de données

Migrations crée une capture instantanée du modèle de données actuel dans Migrations/SchoolContextModelSnapshot.cs. Quand une migration est ajoutée, EF détermine ce qui a changé en comparant le modèle de données actif au fichier de capture instantanée.

Comme le fichier de capture instantané suit l’état du modèle de données, il n’est pas possible de supprimer une migration en supprimant le fichier <timestamp>_<migrationname>.cs. Pour annuler la migration la plus récente, utilisez la commande migrations remove. migrations remove supprime la migration et garantit que la capture instantanée est correctement réinitialisée. Pour plus d’informations, consultez dotnet ef migrations remove.

Consultez Réinitialiser toutes les migrations pour supprimer toutes les migrations.

Application de migrations en production

Nous déconseillons l’appel de Database.Migrate dans les applications de production pendant leur démarrage. Migrate ne doit pas être appelé à partir d’une application déployée sur une batterie de serveurs. Si un scale-out de plusieurs instances de serveur a lieu sur l’application, il est difficile de vérifier que les mises à jour du schéma de base de données ne se produisent pas à partir de plusieurs serveurs ou qu’elles ne sont pas en conflit avec un accès en lecture/écriture.

La migration de base de données doit être effectuée dans le cadre du déploiement et de manière contrôlée. Parmi les approches de migration de base de données de production, citons :

  • L’utilisation de migrations pour créer des scripts SQL et l’utilisation de scripts SQL dans le déploiement
  • L’exécution de dotnet ef database update à partir d’un environnement contrôlé

Résolution des problèmes

Si l’application utilise la Base de données locale SQL Server et affiche l’exception suivante :

SqlException: Cannot open database "ContosoUniversity" requested by the login.
The login failed.
Login failed for user 'user name'.

La solution peut consister à exécuter dotnet ef database update à partir d’une invite de commandes.

Ressources supplémentaires

Étapes suivantes

Le tutoriel suivant crée le modèle de données en ajoutant des propriétés d’entité et de nouvelles entités.

Dans ce tutoriel, nous allons utiliser la fonctionnalité de migrations EF Core pour gérer les modifications du modèle de données.

Si vous rencontrez des problèmes que vous ne pouvez pas résoudre, téléchargez l’application terminée.

Quand une nouvelle application est développée, le modèle de données change fréquemment. Chaque fois que le modèle change, il est désynchronisé avec la base de données. Ce didacticiel commence par configurer Entity Framework pour créer la base de données si elle n’existe pas. Chaque fois que le modèle de données change :

  • La base de données est supprimée
  • EF crée une nouvelle base de données qui correspond au modèle
  • L’application amorce la base de données avec des données de test

Cette approche pour conserver la synchronisation de la base de données avec le modèle de données fonctionne bien jusqu’à ce que l’application doive être déployée en production. Quand l’application s’exécute en production, elle stocke généralement des données qui doivent être tenues à jour. L’application ne peut pas commencer avec une base de données de test chaque fois qu’une modification est apportée (par exemple en cas d’ajout d’une nouvelle colonne). La fonctionnalité de migrations EF Core résout ce problème en permettant à EF Core de mettre à jour le schéma de base de données au lieu de créer une nouvelle base de données.

Plutôt que de supprimer et de recréer la base de données quand le modèle de données change, les migrations mettent à jour le schéma et conservent les données existantes.

Supprimer la base de données

Utilisez l’Explorateur d’objets SQL Server (SSOX) ou la commande database drop :

Dans la console du Gestionnaire de package, exécutez la commande suivante :

Drop-Database

Exécutez Get-Help about_EntityFrameworkCore à partir de la console du Gestionnaire de package pour obtenir des informations d’aide.

Créer une migration initiale et mettre à jour la base de données

Générez le projet et créez la première migration.

Add-Migration InitialCreate
Update-Database

Examiner les méthodes Up et Down

La commande EF Coremigrations add a généré du code pour créer la base de données. Ce code de migration se trouve dans le fichier Migrations\<timestamp>_InitialCreate.cs. La méthode Up de la classe InitialCreate crée les tables de base de données qui correspondent aux jeux d’entités du modèle de données. La méthode Down les supprime, comme indiqué dans l’exemple suivant :

public partial class InitialCreate : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Course",
            columns: table => new
            {
                CourseID = table.Column<int>(nullable: false),
                Title = table.Column<string>(nullable: true),
                Credits = table.Column<int>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Course", x => x.CourseID);
            });

        migrationBuilder.CreateTable(
    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(
            name: "Enrollment");

        migrationBuilder.DropTable(
            name: "Course");

        migrationBuilder.DropTable(
            name: "Student");
    }
}

La fonctionnalité Migrations appelle la méthode Up pour implémenter les modifications du modèle de données pour une migration. Quand une commande est entrée pour annuler la mise à jour, Migrations appelle la méthode Down.

Le code précédent concerne la migration initiale. Ce code a été créé quand la commande migrations add InitialCreate a été exécutée. Le paramètre de nom de migration (« InitialCreate » dans l’exemple) est utilisé comme nom de fichier. Le nom de la migration peut être n’importe quel nom de fichier valide. Nous vous conseillons néanmoins de choisir un mot ou une expression qui résume ce qui est effectué dans la migration. Par exemple, une migration ajoutant une table de département pourrait se nommer « TableAjoutDépartement ».

Si la migration initiale est créée et que la base de données existe :

  • Le code de création de base de données est généré
  • Le code de création de base de données n’a pas besoin de s’exécuter, car la base de données correspond déjà au modèle de données. Si le code de création de base de données est exécuté, il n’apporte aucune modification, car la base de données correspond déjà au modèle de données.

Quand l’application est déployée sur un nouvel environnement, vous devez exécuter le code de création de base de données pour créer la base de données.

Comme la base de données a été supprimée et n’existe pas, les migrations créent une autre base de données.

Capture instantanée du modèle de données

Migrations crée une capture instantanée du schéma de base de données actuel dans Migrations/SchoolContextModelSnapshot.cs. Quand vous ajoutez une migration, EF détermine ce qui a changé en comparant le modèle de données au fichier de capture instantanée.

Pour supprimer une migration, utilisez la commande suivante :

Remove-Migration

Pour supprimer les migrations, la commande supprime la migration et garantit que l’instantané est correctement réinitialisé.

Supprimer EnsureCreated et tester l’application

Dans les phases initiales de développement, nous avons utilisé EnsureCreated. Dans ce tutoriel, nous utilisons des migrations. La commande EnsureCreated a les limitations suivantes :

  • Elle ignore les migrations et crée la base de données et le schéma
  • Elle ne crée pas de table de migrations
  • Elle ne peut pas être utilisée avec des migrations
  • Elle est conçue pour effectuer des tests et un prototypage rapide, où la base de données est supprimée et recréée fréquemment.

Supprimez EnsureCreated :

context.Database.EnsureCreated();

Exécutez l’application et vérifiez que la base de données est amorcée.

Inspecter la base de données

Utilisez l’Explorateur d’objets SQL Server pour inspecter la base de données. Notez l’ajout d’une table __EFMigrationsHistory. La table __EFMigrationsHistory effectue le suivi des migrations qui ont été appliquées à la base de données. Visualisez les données dans la table __EFMigrationsHistory ; elle affiche une ligne pour la première migration. Le dernier journal dans l’exemple de sortie CLI précédent montre l’instruction INSERT qui crée cette ligne.

Exécutez l’application et vérifiez que tout fonctionne.

Application de migrations en production

Nous vous recommandons de faire en sorte que les applications de production n’appellent pasDatabase.Migrate au démarrage de l’application. Migrate ne doit pas être appelée à partir d’une application dans la batterie de serveurs, par exemple si l’application a été déployée dans le cloud avec montée en puissance parallèle (plusieurs instances de l’application sont en cours d’exécution).

La migration de base de données doit être effectuée dans le cadre du déploiement et de manière contrôlée. Parmi les approches de migration de base de données de production, citons :

  • L’utilisation de migrations pour créer des scripts SQL et l’utilisation de scripts SQL dans le déploiement
  • L’exécution de dotnet ef database update à partir d’un environnement contrôlé

EF Core utilise la table __MigrationsHistory pour voir si des migrations doivent s’exécuter. Si la base de données est à jour, aucune migration n’est exécutée.

Résolution des problèmes

Téléchargez l’application terminée.

L’application génère l’exception suivante :

SqlException: Cannot open database "ContosoUniversity" requested by the login.
The login failed.
Login failed for user 'user name'.

Solution : Exécutez dotnet ef database update.

Ressources supplémentaires