Tutoriel : Implémenter l’héritage - ASP.NET MVC avec EF CoreTutorial: Implement inheritance - ASP.NET MVC with EF Core

Dans le didacticiel précédent, vous avez géré les exceptions d’accès concurrentiel.In the previous tutorial, you handled concurrency exceptions. Ce didacticiel vous indiquera comment implémenter l’héritage dans le modèle de données.This tutorial will show you how to implement inheritance in the data model.

En programmation orientée objet, vous pouvez utiliser l’héritage pour faciliter la réutilisation du code.In object-oriented programming, you can use inheritance to facilitate code reuse. Dans ce didacticiel, vous allez modifier les classes Instructor et Student afin qu’elles dérivent d’une classe de base Person qui contient des propriétés telles que LastName, communes aux formateurs et aux étudiants.In this tutorial, you'll change the Instructor and Student classes so that they derive from a Person base class which contains properties such as LastName that are common to both instructors and students. Vous n’ajouterez ni ne modifierez aucune page web, mais vous modifierez une partie du code et ces modifications seront automatiquement répercutées dans la base de données.You won't add or change any web pages, but you'll change some of the code and those changes will be automatically reflected in the database.

Dans ce didacticiel, vous avez effectué les actions suivantes :In this tutorial, you:

  • Mapper l’héritage à la base de donnéesMap inheritance to database
  • Créer la classe PersonCreate the Person class
  • Mettre à jour Student et InstructorUpdate Instructor and Student
  • Ajouter la classe Person au modèleAdd Person to the model
  • Créer et mettre à jour des migrationsCreate and update migrations
  • Tester l’implémentationTest the implementation

PrérequisPrerequisites

Mapper l’héritage à la base de donnéesMap inheritance to database

Les classes Instructor et Student du modèle de données School ont plusieurs propriétés identiques :The Instructor and Student classes in the School data model have several properties that are identical:

Classes Student et Instructor

Supposons que vous souhaitez éliminer le code redondant pour les propriétés partagées par les entités Instructor et Student.Suppose you want to eliminate the redundant code for the properties that are shared by the Instructor and Student entities. Ou vous souhaitez écrire un service capable de mettre en forme les noms sans se soucier du fait que le nom provienne d’un formateur ou d’un étudiant.Or you want to write a service that can format names without caring whether the name came from an instructor or a student. Vous pouvez créer une classe de base Person qui contient uniquement les propriétés partagées, puis paramétrer les classes Instructor et Student pour qu’elles héritent de cette classe de base, comme indiqué dans l’illustration suivante :You could create a Person base class that contains only those shared properties, then make the Instructor and Student classes inherit from that base class, as shown in the following illustration:

Classes Student et Instructor dérivant de la classe Person

Il existe plusieurs façons de représenter cette structure d’héritage dans la base de données.There are several ways this inheritance structure could be represented in the database. Vous pouvez avoir une table de personnes qui inclut des informations sur les étudiants et les formateurs dans une table unique.You could have a Person table that includes information about both students and instructors in a single table. Certaines des colonnes pourraient s’appliquer uniquement aux formateurs (HireDate), certaines uniquement aux étudiants (EnrollmentDate) et certaines aux deux (LastName, FirstName).Some of the columns could apply only to instructors (HireDate), some only to students (EnrollmentDate), some to both (LastName, FirstName). En règle générale, vous pouvez avoir une colonne de discriminateur pour indiquer le type que chaque ligne représente.Typically, you'd have a discriminator column to indicate which type each row represents. Par exemple, la colonne de discriminateur peut avoir « Instructor » pour les formateurs et « Student » pour les étudiants.For example, the discriminator column might have "Instructor" for instructors and "Student" for students.

Exemple TPH (table par hiérarchie)

Ce modèle de génération d’une structure d’héritage d’entité à partir d’une table de base de données unique porte le nom d’héritage TPH (table par hiérarchie).This pattern of generating an entity inheritance structure from a single database table is called table-per-hierarchy (TPH) inheritance.

Une alternative consiste à faire en sorte que la base de données ressemble plus à la structure d’héritage.An alternative is to make the database look more like the inheritance structure. Par exemple, vous pouvez avoir uniquement les champs de nom dans la table Person, et des tables Instructor et Student distinctes avec les champs de date.For example, you could have only the name fields in the Person table and have separate Instructor and Student tables with the date fields.

Héritage TPT (table par type)

Ce modèle consistant à créer une table de base de données pour chaque classe d’entité est appelé héritage TPT (table par type).This pattern of making a database table for each entity class is called table per type (TPT) inheritance.

Une autre option encore consiste à mapper tous les types non abstraits à des tables individuelles.Yet another option is to map all non-abstract types to individual tables. Toutes les propriétés d’une classe, y compris les propriétés héritées, sont mappées aux colonnes de la table correspondante.All properties of a class, including inherited properties, map to columns of the corresponding table. Ce modèle porte le nom d’héritage TPC (table par classe concrète).This pattern is called Table-per-Concrete Class (TPC) inheritance. Si vous avez implémenté l’héritage TPC pour les classes Person, Student et Instructor comme indiqué précédemment, les tables Student et Instructor ne seraient pas différentes avant et après l’implémentation de l’héritage.If you implemented TPC inheritance for the Person, Student, and Instructor classes as shown earlier, the Student and Instructor tables would look no different after implementing inheritance than they did before.

Les modèles d’héritage TPC et TPH fournissent généralement de meilleures performances que les modèles d’héritage TPT, car les modèles TPT peuvent entraîner des requêtes de jointure complexes.TPC and TPH inheritance patterns generally deliver better performance than TPT inheritance patterns, because TPT patterns can result in complex join queries.

Ce didacticiel montre comment implémenter l’héritage TPH.This tutorial demonstrates how to implement TPH inheritance. TPH est le seul modèle d’héritage pris en charge par Entity Framework Core.TPH is the only inheritance pattern that the Entity Framework Core supports. Vous allez créer une classe Person, modifier les classes Instructor et Student à dériver de Person, ajouter la nouvelle classe à DbContext et créer une migration.What you'll do is create a Person class, change the Instructor and Student classes to derive from Person, add the new class to the DbContext, and create a migration.

Conseil

Pensez à enregistrer une copie du projet avant d’apporter les modifications suivantes.Consider saving a copy of the project before making the following changes. Ensuite, si vous rencontrez des problèmes et devez recommencer, il sera plus facile de démarrer à partir du projet enregistré que d’annuler les étapes effectuées pour ce didacticiel ou de retourner au début de la série entière.Then if you run into problems and need to start over, it will be easier to start from the saved project instead of reversing steps done for this tutorial or going back to the beginning of the whole series.

Créer la classe PersonCreate the Person class

Dans le dossier Models, créez Person.cs et remplacez le code du modèle par le code suivant :In the Models folder, create Person.cs and replace the template code with the following code:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public abstract class Person
    {
        public int ID { get; set; }

        [Required]
        [StringLength(50)]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
        [Required]
        [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
        [Column("FirstName")]
        [Display(Name = "First Name")]
        public string FirstMidName { get; set; }

        [Display(Name = "Full Name")]
        public string FullName
        {
            get
            {
                return LastName + ", " + FirstMidName;
            }
        }
    }
}

Mettre à jour Student et InstructorUpdate Instructor and Student

Dans Instructor.cs, dérivez la classe Instructor de la classe Person et supprimez les champs de clé et de nom.In Instructor.cs, derive the Instructor class from the Person class and remove the key and name fields. Le code ressemblera à l’exemple suivant :The code will look like the following example:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Instructor : Person
    {
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Hire Date")]
        public DateTime HireDate { get; set; }

        public ICollection<CourseAssignment> CourseAssignments { get; set; }
        public OfficeAssignment OfficeAssignment { get; set; }
    }
}

Apportez les mêmes modifications dans Student.cs.Make the same changes in Student.cs.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Student : Person
    {
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Enrollment Date")]
        public DateTime EnrollmentDate { get; set; }


        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

Ajouter la classe Person au modèleAdd Person to the model

Ajoutez le type d’entité Person à SchoolContext.cs.Add the Person entity type to SchoolContext.cs. Les nouvelles lignes apparaissent en surbrillance.The new lines are highlighted.

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
        {
        }

        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }
        public DbSet<Department> Departments { get; set; }
        public DbSet<Instructor> Instructors { get; set; }
        public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
        public DbSet<CourseAssignment> CourseAssignments { get; set; }
        public DbSet<Person> People { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
            modelBuilder.Entity<Department>().ToTable("Department");
            modelBuilder.Entity<Instructor>().ToTable("Instructor");
            modelBuilder.Entity<OfficeAssignment>().ToTable("OfficeAssignment");
            modelBuilder.Entity<CourseAssignment>().ToTable("CourseAssignment");
            modelBuilder.Entity<Person>().ToTable("Person");

            modelBuilder.Entity<CourseAssignment>()
                .HasKey(c => new { c.CourseID, c.InstructorID });
        }
    }
}

C’est là tout ce dont Entity Framework a besoin pour configurer l’héritage TPH (table par hiérarchie).This is all that the Entity Framework needs in order to configure table-per-hierarchy inheritance. Comme vous le verrez, lorsque la base de données sera mise à jour, elle aura une table Person à la place des tables Student et Instructor.As you'll see, when the database is updated, it will have a Person table in place of the Student and Instructor tables.

Créer et mettre à jour des migrationsCreate and update migrations

Enregistrez vos modifications et générez le projet.Save your changes and build the project. Ensuite, ouvrez la fenêtre de commande dans le dossier du projet et entrez la commande suivante :Then open the command window in the project folder and enter the following command:

dotnet ef migrations add Inheritance

N’exécutez pas encore la commande database update.Don't run the database update command yet. Cette commande entraîne une perte de données, car elle supprime la table Instructor et renomme la table Student en Person.That command will result in lost data because it will drop the Instructor table and rename the Student table to Person. Vous devez fournir un code personnalisé pour préserver les données existantes.You need to provide custom code to preserve existing data.

Ouvrez Migrations/<horodatage>_Inheritance.cs et remplacez la méthode Up par le code suivant :Open Migrations/<timestamp>_Inheritance.cs and replace the Up method with the following code:

protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.DropForeignKey(
        name: "FK_Enrollment_Student_StudentID",
        table: "Enrollment");

    migrationBuilder.DropIndex(name: "IX_Enrollment_StudentID", table: "Enrollment");

    migrationBuilder.RenameTable(name: "Instructor", newName: "Person");
    migrationBuilder.AddColumn<DateTime>(name: "EnrollmentDate", table: "Person", nullable: true);
    migrationBuilder.AddColumn<string>(name: "Discriminator", table: "Person", nullable: false, maxLength: 128, defaultValue: "Instructor");
    migrationBuilder.AlterColumn<DateTime>(name: "HireDate", table: "Person", nullable: true);
    migrationBuilder.AddColumn<int>(name: "OldId", table: "Person", nullable: true);

    // Copy existing Student data into new Person table.
    migrationBuilder.Sql("INSERT INTO dbo.Person (LastName, FirstName, HireDate, EnrollmentDate, Discriminator, OldId) SELECT LastName, FirstName, null AS HireDate, EnrollmentDate, 'Student' AS Discriminator, ID AS OldId FROM dbo.Student");
    // Fix up existing relationships to match new PK's.
    migrationBuilder.Sql("UPDATE dbo.Enrollment SET StudentId = (SELECT ID FROM dbo.Person WHERE OldId = Enrollment.StudentId AND Discriminator = 'Student')");

    // Remove temporary key
    migrationBuilder.DropColumn(name: "OldID", table: "Person");

    migrationBuilder.DropTable(
        name: "Student");

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

    migrationBuilder.AddForeignKey(
        name: "FK_Enrollment_Person_StudentID",
        table: "Enrollment",
        column: "StudentID",
        principalTable: "Person",
        principalColumn: "ID",
        onDelete: ReferentialAction.Cascade);
}

Ce code prend en charge les tâches de mise à jour de base de données suivantes :This code takes care of the following database update tasks:

  • Supprime les contraintes de clé étrangère et les index qui pointent vers la table Student.Removes foreign key constraints and indexes that point to the Student table.

  • Renomme la table Instructor en Person et apporte les modifications nécessaires pour qu’elle stocke les données des étudiants :Renames the Instructor table as Person and makes changes needed for it to store Student data:

  • Ajoute une EnrollmentDate nullable pour les étudiants.Adds nullable EnrollmentDate for students.

  • Ajoute la colonne Discriminator pour indiquer si une ligne est pour un étudiant ou un formateur.Adds Discriminator column to indicate whether a row is for a student or an instructor.

  • Rend HireDate nullable étant donné que les lignes d’étudiant n’ont pas de dates d’embauche.Makes HireDate nullable since student rows won't have hire dates.

  • Ajoute un champ temporaire qui sera utilisé pour mettre à jour les clés étrangères qui pointent vers les étudiants.Adds a temporary field that will be used to update foreign keys that point to students. Lorsque vous copiez des étudiants dans la table Person, ils obtiennent de nouvelles valeurs de clés primaires.When you copy students into the Person table they will get new primary key values.

  • Copie des données à partir de la table Student dans la table Person.Copies data from the Student table into the Person table. Cela entraîne l’affectation de nouvelles valeurs de clés primaires aux étudiants.This causes students to get assigned new primary key values.

  • Corrige les valeurs de clés étrangères qui pointent vers les étudiants.Fixes foreign key values that point to students.

  • Crée de nouveau les index et les contraintes de clé étrangère, désormais pointées vers la table Person.Re-creates foreign key constraints and indexes, now pointing them to the Person table.

(Si vous aviez utilisé un GUID à la place d’un entier comme type de clé primaire, les valeurs des clés primaires des étudiants n’auraient pas changé, et plusieurs de ces étapes auraient pu être omises.)(If you had used GUID instead of integer as the primary key type, the student primary key values wouldn't have to change, and several of these steps could have been omitted.)

Exécutez la commande database update :Run the database update command:

dotnet ef database update

(Dans un système de production, vous apporteriez les modifications correspondantes à la méthode Down au cas où vous auriez à l’utiliser pour revenir à la version précédente de la base de données.(In a production system you would make corresponding changes to the Down method in case you ever had to use that to go back to the previous database version. Pour ce didacticiel, vous n’utiliserez pas la méthode Down.)For this tutorial you won't be using the Down method.)

Notes

Vous pouvez obtenir d’autres erreurs en apportant des modifications au schéma dans une base de données qui comporte déjà des données.It's possible to get other errors when making schema changes in a database that has existing data. Si vous obtenez des erreurs de migration que vous ne pouvez pas résoudre, vous pouvez changer le nom de la base de données dans la chaîne de connexion ou supprimer la base de données.If you get migration errors that you can't resolve, you can either change the database name in the connection string or delete the database. Avec une nouvelle base de données, il n’y a pas de données à migrer et la commande de mise à jour de base de données a plus de chances de s’exécuter sans erreur.With a new database, there's no data to migrate, and the update-database command is more likely to complete without errors. Pour supprimer la base de données, utilisez SSOX ou exécutez la commande CLI database drop.To delete the database, use SSOX or run the database drop CLI command.

Tester l’implémentationTest the implementation

Exécutez l’application et essayez différentes pages.Run the app and try various pages. Tout fonctionne comme avant.Everything works the same as it did before.

Dans l’Explorateur d’objets SQL Server, développez Data Connections/SchoolContext puis Tables, et vous constatez que les tables Student et Instructor ont été remplacées par une table Person.In SQL Server Object Explorer, expand Data Connections/SchoolContext and then Tables, and you see that the Student and Instructor tables have been replaced by a Person table. Ouvrez le concepteur de la table Person et vous constatez qu’elle possède toutes les colonnes qui existaient dans les tables Student et Instructor.Open the Person table designer and you see that it has all of the columns that used to be in the Student and Instructor tables.

Table Person dans SSOX

Cliquez avec le bouton droit sur la table Person, puis cliquez sur Afficher les données de la table pour voir la colonne de discriminateur.Right-click the Person table, and then click Show Table Data to see the discriminator column.

Table Person dans SSOX - données de la table

Obtenir le codeGet the code

Télécharger ou afficher l’application complète.Download or view the completed application.

Ressources supplémentairesAdditional resources

Pour plus d’informations sur l’héritage dans Entity Framework Core, consultez Héritage.For more information about inheritance in Entity Framework Core, see Inheritance.

Étapes suivantesNext steps

Dans ce didacticiel, vous avez effectué les actions suivantes :In this tutorial, you:

  • Mappez l’héritage à la base de donnéesMapped inheritance to database
  • Créez la classe PersonCreated the Person class
  • Mettez à jour Student et InstructorUpdated Instructor and Student
  • Classe Person ajoutée au modèleAdded Person to the model
  • Migrations créées et mises à jourCreated and update migrations
  • Testez l’implémentationTested the implementation

Passez au tutoriel suivant pour découvrir comment gérer divers scénarios Entity Framework relativement avancés.Advance to the next tutorial to learn how to handle a variety of relatively advanced Entity Framework scenarios.