Exercice - Configurer une migration

Effectué

Dans cette unité, vous allez créer des classes d’entité C# mappées à des tables dans une base de données SQLite locale. La fonctionnalité de migration EF Core produit des tables à partir de ces entités.

Les migrations sont un moyen de mettre à jour de façon incrémentielle le schéma de la base de données.

Obtenir les fichiers projet

Pour commencer, récupérez les fichiers projet. Pour obtenir les fichiers projet, plusieurs options s’offrent à vous :

  • Utiliser GitHub Codespaces
  • Cloner le dépôt GitHub

Si vous avez un runtime de conteneur compatible installé, vous pouvez également utiliser l’extension Dev Containers pour ouvrir le dépôt dans un conteneur avec les outils préinstallés.

Utiliser GitHub Codespaces

Un codespace est un IDE hébergé dans le cloud. Si vous utilisez GitHub Codespaces, accédez au dépôt dans votre navigateur. Sélectionnez Code, puis créez un codespace dans la branche main.

Cloner le dépôt GitHub

Si vous n’utilisez pas GitHub Codespaces, vous pouvez cloner le dépôt GitHub du projet, puis ouvrir les fichiers en tant que dossier dans Visual Studio Code.

  1. Ouvrez un terminal de commandes, puis clonez le projet à partir de GitHub en utilisant l’invite de commandes suivante :

    git clone https://github.com/MicrosoftDocs/mslearn-persist-data-ef-core
    
  2. Accédez au dossier mslearn-persist-data-ef-core, puis ouvrez le projet dans Visual Studio Code :

    cd mslearn-persist-data-ef-core
    code .
    

Vérifier le code

Vous disposez maintenant des fichiers projet nécessaires. Examinez le contenu du projet et passez en revue le code.

  • Le projet, une API web ASP.NET Core, se trouve dans le répertoire ContosoPizza. Les chemins de fichiers auxquels il est fait référence dans ce module sont relatifs au répertoire ContosoPizza.
  • Services/PizzaService.cs est une classe de service qui définit les méthodes CRUD (création, lecture, mise à jour et suppression). Toutes les méthodes lèvent actuellement System.NotImplementedException.
  • Dans Program.cs, PizzaService est inscrit auprès du système d’injection de dépendances ASP.NET Core.
  • Controllers/PizzaController.cs est une valeur pour ApiController qui expose un point de terminaison pour les verbes HTTP POST, GET, PUT et DELETE. Ces verbes appellent les méthodes CRUD correspondantes sur PizzaService. PizzaService est injecté dans le constructeur PizzaController.
  • Le dossier Models contient les modèles utilisés par PizzaService et PizzaController.
  • Les modèles d’entité, Pizza.cs, Topping.cs et Sauce.cs, ont les relations suivantes :
    • Une pizza peut avoir une ou plusieurs garnitures.
    • Une garniture peut être utilisée sur une ou plusieurs pizzas.
    • Une pizza ne peut avoir qu’une seule sauce, mais une même sauce peut être utilisée sur plusieurs pizzas.

Générer l’application

Pour générer l’application dans Visual Studio Code :

  1. Dans le volet Explorateur, cliquez avec le bouton droit sur le répertoire ContosoPizza et sélectionnez Ouvrir dans le terminal intégré.

    Un volet de terminal délimité au répertoire ContosoPizza s’ouvre.

  2. Générez l’application en utilisant la commande suivante :

    dotnet build
    

    Le code doit être généré sans avertissement ni erreur.

Ajouter des packages de NuGet et des outils de EF Core

Le moteur de base de données que vous utilisez dans ce module est SQLite. SQLite est un moteur de base de données léger basé sur des fichiers. C’est un bon choix pour le développement et les tests, et c’est aussi un bon choix pour les déploiements en production à petite échelle.

Notes

Comme indiqué précédemment, les fournisseurs de base de données dans EF Core sont enfichables. SQLite est un bon choix pour ce module, car il est léger et multiplateforme. Vous pouvez utiliser le même code pour travailler avec différents moteurs de base de données, tels que SQL Server et PostgreSQL. Vous pouvez même utiliser plusieurs moteurs de base de données dans la même application.

Avant de commencer, ajoutez les packages obligatoires :

  1. Dans le volet du terminal, exécutez la commande suivante :

    dotnet add package Microsoft.EntityFrameworkCore.Sqlite
    

    Cette commande ajoute le package NuGet qui contient le fournisseur de bases de données EF Core SQLite et toutes ses dépendances, y compris les services EF Core courants.

  2. Exécutez cette commande :

    dotnet add package Microsoft.EntityFrameworkCore.Design
    

    Cette commande ajoute les packages obligatoires pour les outils EF Core.

  3. Pour terminer, exécutez cette commande :

    dotnet tool install --global dotnet-ef
    

    cette commande installe dotnet ef, l’outil que vous allez utiliser pour créer des migrations et la génération de modèles automatique.

    Conseil

    Si dotnet ef est déjà installé, vous pouvez le mettre à jour en exécutant dotnet tool update --global dotnet-ef.

Échafauder des modèles et DbContext

Vous allez à présent ajouter et configurer une implémentation de DbContext. DbContext est une passerelle qui vous permet d’interagir avec la base de données.

  1. Cliquez avec le bouton droit sur le répertoire ContosoPizza et ajoutez un nouveau dossier appelé Data.

  2. Dans le dossier Data, créez un fichier nommé PizzaContext.cs. Ajoutez le code suivant au fichier vide :

    using Microsoft.EntityFrameworkCore;
    using ContosoPizza.Models;
    
    namespace ContosoPizza.Data;
    
    public class PizzaContext : DbContext
    {
        public PizzaContext (DbContextOptions<PizzaContext> options)
            : base(options)
        {
        }
    
        public DbSet<Pizza> Pizzas => Set<Pizza>();
        public DbSet<Topping> Toppings => Set<Topping>();
        public DbSet<Sauce> Sauces => Set<Sauce>();
    }
    

    Dans le code précédent :

    • Le constructeur accepte un paramètre de type DbContextOptions<PizzaContext>. Le constructeur permet au code externe de passer la configuration. Ainsi, le DbContext peut être partagé entre le code de test et le code de production, et même utilisé avec différents fournisseurs.
    • Les propriétés DbSet<T> correspondent aux tables à créer dans la base de données.
    • Les noms des tables correspondront aux noms des propriétés DbSet<T> dans la classe PizzaContext. Vous pouvez remplacer ce comportement si nécessaire.
    • Lorsqu'il est instancié, PizzaContext expose les propriétés Pizzas, Toppings et Sauces. Les modifications que vous apportez aux collections exposées par ces propriétés sont propagées à la base de données.
  3. Dans Program.cs, remplacez // Add the PizzaContext par le code suivant :

    builder.Services.AddSqlite<PizzaContext>("Data Source=ContosoPizza.db");
    

    Le code précédent :

    • Inscrit PizzaContext auprès du système d’injection de dépendances ASP.NET Core.
    • Spécifie que PizzaContext utilisera le fournisseur de bases de données SQLite.
    • Définit une chaîne de connexion SQLite qui pointe vers un fichier local, ContosoPizza.db.

    Notes

    SQLite utilise des fichiers de base de données locaux. Il est donc probablement acceptable de coder en dur la chaîne de connexion. Pour les bases de données réseau telles que PostgreSQL et SQL Server, vous devez toujours stocker vos chaînes de connexion de manière sécurisée. Pour le développement local, utilisez le Gestionnaire de secrets. Pour les déploiements de production, envisagez d’utiliser un service comme Azure Key Vault.

  4. Également dans Program.cs, remplacez // Additional using declarations par le code suivant.

    using ContosoPizza.Data;
    

    Ce code résout les dépendances de l’étape précédente.

  5. Enregistrez toutes vos modifications. GitHub Codespaces enregistre automatiquement vos modifications.

  6. Générez l’application dans le terminal en exécutant dotnet build. La génération doit normalement réussir sans avertissement ni erreur.

Créer et exécuter un projet de migration

Ensuite, créez une migration que vous pouvez utiliser pour créer votre base de données initiale.

  1. Exécutez la commande suivante pour générer une migration afin de créer les tables de base de données :

    dotnet ef migrations add InitialCreate --context PizzaContext
    

    Dans la commande précédente :

    • La migration reçoit le nom de InitialCreate.
    • L’option --context spécifie le nom de la classe dans le projet ContosoPizza, qui dérive de DbContext.

    Un nouveau répertoire Migrations apparaît à la racine du projet ContosoPizza. Le répertoire contient un fichier <timestamp>_InitialCreate.cs qui décrit les modifications de la base de données à traduire en script de modification du langage de définition de données (DDL).

  2. Exécutez la commande suivante pour appliquer la migration InitialCreate :

    dotnet ef database update --context PizzaContext
    

    Cette commande applique la migration. Comme ContosoPizza.db n’existe pas, la migration est créée dans le répertoire du projet.

    Conseil

    L'outil dotnet ef est pris en charge sur toutes les plateformes. Dans Visual Studio sur Windows, vous pouvez utiliser les applets de commande PowerShell Add-Migration et Update-Database dans la fenêtre intégrée Console du gestionnaire de package.

Inspecter la base de données

EF Core a créé une base de données pour votre application. Examinez ensuite le contenu de la base de données à l’aide de l’extension SQLite.

  1. Dans le volet Explorateur, cliquez avec le bouton droit sur le fichier ContosoPizza.db et sélectionnez Ouvrir la base de données.

    Capture d’écran montrant l’option de menu Ouvrir la base de données dans le volet Explorateur de Visual Studio Code.

    Un dossier SQLite Explorer apparaît dans le volet Explorateur.

    Capture d’écran montrant le dossier SQLite Explorer dans le volet Explorateur.

  2. Sélectionnez le dossier SQLite Explorer pour développer le nœud et tous ses nœuds enfants. Cliquez avec le bouton droit sur ContosoPizza.db et sélectionnez Afficher la table « sqlite_master » pour afficher le schéma complet de la base de données et les contraintes créées par la migration.

    Capture d’écran montrant le dossier SQLite Explorer développé dans le volet Explorateur.

    • Des tables correspondant à chaque entité ont été créées.
    • Les noms des tableaux ont été extraits des noms des propriétés DbSet dans PizzaContext.
    • Les propriétés nommées Id ont été déduites pour l’auto-incrémentation des champs de clé primaire.
    • Les conventions de nommage des contraintes de clé primaire et de clé étrangère d’EF Core sont respectivement PK_<primary key property> et FK_<dependent entity>_<principal entity>_<foreign key property>. Les espaces réservés <dependent entity> et <principal entity> correspondent aux noms des classes d’entité.

    Notes

    Comme ASP.NET Core MVC, EF Core utilise une approche de type « convention plutôt que configuration ». Les conventions EF Core raccourcissent le temps de développement en inférant l’intention du développeur. Par exemple, une propriété nommée Id ou <entity name>Id est inférée comme étant la clé primaire de la table générée. Si vous choisissez de ne pas adopter la convention d’affectation de noms, la propriété doit être annotée avec l’attribut [Key] ou configurée en tant que clé dans la méthode OnModelCreating de DbContext.

Modifier le modèle et mettre à jour le schéma de la base de données

Votre responsable chez Contoso Pizza vous communique de nouvelles exigences qui vous obligent à changer vos modèles d’entité. Dans les étapes suivantes, vous allez modifier les modèles en utilisant des attributs de mappage (parfois appelés « annotations de données »).

  1. Dans Models\Pizza.cs, apportez les modifications suivantes :

    1. Ajoutez une directive using pour System.ComponentModel.DataAnnotations.
    2. Ajoutez un attribut [Required] avant la propriété Name pour marquer la propriété comme requis.
    3. Ajoutez un attribut [MaxLength(100)] avant la propriété Name pour spécifier une longueur de chaîne maximale de 100.
    using System.ComponentModel.DataAnnotations;
    
    namespace ContosoPizza.Models;
    
    public class Pizza
    {
        public int Id { get; set; }
    
        [Required]
        [MaxLength(100)]
        public string? Name { get; set; }
    
        public Sauce? Sauce { get; set; }
    
        public ICollection<Topping>? Toppings { get; set; }
    }
    
  2. Dans Models\Sauce.cs, apportez les modifications suivantes :

    1. Ajoutez une directive using pour System.ComponentModel.DataAnnotations.
    2. Ajoutez un attribut [Required] avant la propriété Name pour marquer la propriété comme requis.
    3. Ajoutez un attribut [MaxLength(100)] avant la propriété Name pour spécifier une longueur de chaîne maximale de 100.
    4. Ajoutez une propriété bool nommée IsVegan.
    using System.ComponentModel.DataAnnotations;
    
    namespace ContosoPizza.Models;
    
    public class Sauce
    {
        public int Id { get; set; }
    
        [Required]
        [MaxLength(100)]
        public string? Name { get; set; }
    
        public bool IsVegan { get; set; }
    }
    
  3. Dans Models\Topping.cs, apportez les modifications suivantes :

    1. Ajoutez des directives using pour System.ComponentModel.DataAnnotations et System.Text.Json.Serialization.
    2. Ajoutez un attribut [Required] avant la propriété Name pour marquer la propriété comme requis.
    3. Ajoutez un attribut [MaxLength(100)] avant la propriété Name pour spécifier une longueur de chaîne maximale de 100.
    4. Ajoutez une propriété decimal nommée Calories juste après la propriété Name.
    5. Ajoutez une propriété Pizzas de type ICollection<Pizza>? pour créer Pizza-Toppingune relation plusieurs-à-plusieurs.
    6. Ajoutez un attribut [JsonIgnore] à la propriété Pizzas.

    Important

    Ces étapes empêchent les entités Topping d’inclure la propriété Pizzas quand le code de l’API web sérialise la réponse au format JSON. Sans ce changement, une collection sérialisée de garnitures inclut une collection de chaque pizza qui utilise la garniture. Chaque pizza de cette collection contient une collection de garnitures, chacun d’entre eux contenant une collection de pizzas. Ce type de boucle infinie est appelé référence circulaire et ne peut pas être sérialisé.

    using System.ComponentModel.DataAnnotations;
    using System.Text.Json.Serialization;
    
    namespace ContosoPizza.Models;
    
    public class Topping
    {
        public int Id { get; set; }
    
        [Required]
        [MaxLength(100)]
        public string? Name { get; set; }
    
        public decimal Calories { get; set; }
    
        [JsonIgnore]
        public ICollection<Pizza>? Pizzas { get; set; }
    }
    
  4. Enregistrez toutes vos modifications et exécutez dotnet build.

  5. Exécutez la commande suivante pour générer une migration afin de créer les tables de base de données :

    dotnet ef migrations add ModelRevisions --context PizzaContext
    

    Une migration nommée ModelRevisions est créée.

    Notes

    Le message suivant s’affiche : Une opération a été échafaudée, ce qui peut entraîner la perte de données. Vérifiez l’exactitude de la migration. Le message s’affiche car vous avez remplacé la relation un-à-plusieurs de Pizza à Topping par une relation plusieurs-à-plusieurs, ce qui nécessite la suppression d’une colonne de clé étrangère existante. Étant donné que vous n’avez pas encore de données dans votre base de données, ce changement n’est pas problématique. Mais en règle générale, il est judicieux de vérifier la migration générée quand cet avertissement s’affiche pour vérifier qu’aucune donnée n’est supprimée ou tronquée par la migration.

  6. Exécutez la commande suivante pour appliquer la migration ModelRevisions :

    dotnet ef database update --context PizzaContext
    
  7. Dans la barre de titre du dossier SQLite Explorer, sélectionnez le bouton Actualiser les bases de données.

    Capture d’écran montrant le bouton Actualiser les bases de données dans la barre de titre de SQLite Explorer.

  8. Dans le dossier SQLite Explorer, cliquez avec le bouton droit sur ContosoPizza.db. Sélectionnez Afficher la table « sqlite_master » pour afficher le schéma et les contraintes de la base de données complète.

    Important

    L’extension SQLite réutilise les onglets SQLite ouverts.

    • Une table de jointure PizzaTopping a été créée pour représenter la relation plusieurs-à-plusieurs entre les pizzas et les garnitures.
    • De nouveaux champs ont été ajoutés à Toppings et à Sauces.
      • Calories est défini en tant que colonne text, car SQLite n’a pas de type decimal correspondant.
      • De même, IsVegan est défini en tant que colonne integer. SQLite ne définit pas de type bool.
      • Dans les deux cas, EF Core gère la traduction.
    • La colonne Name de chaque table a été marquée not null, mais SQLite n’a pas de contrainte MaxLength.

    Conseil

    Les fournisseurs de base de données EF Core mappent un schéma de modèle aux fonctionnalités d’une base de données spécifique. Bien que SQLite n’implémente pas de contrainte correspondante pour MaxLength, d’autres bases de données comme SQL Server et PostgreSQL le font.

  9. Dans le dossier SQLite Explorer, cliquez avec le bouton droit sur la table _EFMigrationsHistory et sélectionnez Afficher la table. La table contient une liste de toutes les migrations appliquées à la base de données. Comme vous avez exécuté deux migrations, il existe deux entrées : une pour la migration InitialCreate et l’autre pour ModelRevisions.

Notes

Dans cet exercice, des attributs de mappage (annotations de données) ont été utilisés pour mapper des modèles à la base de données. Au lieu de mapper des attributs, vous pouvez utiliser l’API Fluent ModelBuilder pour configurer des modèles. Les deux approches sont valides, mais certains développeurs ont une préférence pour une approche plutôt que l’autre.

Vous avez utilisé des migrations pour définir et mettre à jour un schéma de la base de données. Dans l’unité suivante, vous allez terminer les méthodes dans PizzaService qui manipulent les données.

Vérifiez vos connaissances

1.

Dans une classe d’entité, quelle est la convention de nommage des propriétés pour une clé primaire ?