Tutoriel : Créer un modèle de données complexe - ASP.NET MVC avec EF CoreTutorial: Create a complex data model - ASP.NET MVC with EF Core

Dans les didacticiels précédents, vous avez travaillé avec un modèle de données simple composé de trois entités.In the previous tutorials, you worked with a simple data model that was composed of three entities. Dans ce didacticiel, vous allez ajouter des entités et des relations, et vous personnaliserez le modèle de données en spécifiant des règles de mise en forme, de validation et de mappage de base de données.In this tutorial, you'll add more entities and relationships and you'll customize the data model by specifying formatting, validation, and database mapping rules.

Lorsque vous aurez terminé, les classes d’entité composeront le modèle de données complet indiqué dans l’illustration suivante :When you're finished, the entity classes will make up the completed data model that's shown in the following illustration:

Diagramme des entités

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

  • Personnaliser le modèle de donnéesCustomize the Data model
  • Apporter des modifications à l’entité StudentMake changes to Student entity
  • Créer une entité InstructorCreate Instructor entity
  • Créer une entité OfficeAssignmentCreate OfficeAssignment entity
  • Modifier l’entité CourseModify Course entity
  • Créer l’entité DepartmentCreate Department entity
  • Modifier l’entité EnrollmentModify Enrollment entity
  • Mettre à jour le contexte de base de donnéesUpdate the database context
  • Remplir la base de données avec des données de testSeed database with test data
  • Ajouter une migrationAdd a migration
  • Changer la chaîne de connexionChange the connection string
  • Mettre à jour la base de donnéesUpdate the database

PrérequisPrerequisites

Personnaliser le modèle de donnéesCustomize the Data model

Dans cette section, vous allez apprendre à personnaliser le modèle de données en utilisant des attributs qui spécifient des règles de mise en forme, de validation et de mappage de base de données.In this section you'll see how to customize the data model by using attributes that specify formatting, validation, and database mapping rules. Ensuite, dans plusieurs des sections suivantes, vous allez créer le modèle de données School complet en ajoutant des attributs aux classes que vous avez déjà créées et en créant de nouvelles classes pour les autres types d’entités dans le modèle.Then in several of the following sections you'll create the complete School data model by adding attributes to the classes you already created and creating new classes for the remaining entity types in the model.

Attribut DataTypeThe DataType attribute

Pour les dates d’inscription des étudiants, toutes les pages web affichent l’heure avec la date, alors que seule la date vous intéresse dans ce champ.For student enrollment dates, all of the web pages currently display the time along with the date, although all you care about for this field is the date. Vous pouvez avoir recours aux attributs d’annotation de données pour apporter une modification au code, permettant de corriger le format d’affichage dans chaque vue qui affiche ces données.By using data annotation attributes, you can make one code change that will fix the display format in every view that shows the data. Pour voir un exemple de la procédure à suivre, vous allez ajouter un attribut à la propriété EnrollmentDate dans la classe Student.To see an example of how to do that, you'll add an attribute to the EnrollmentDate property in the Student class.

Dans Models/Student.cs, ajoutez une instruction using pour l’espace de noms System.ComponentModel.DataAnnotations et ajoutez les attributs DataType et DisplayFormat à la propriété EnrollmentDate, comme indiqué dans l’exemple suivant :In Models/Student.cs, add a using statement for the System.ComponentModel.DataAnnotations namespace and add DataType and DisplayFormat attributes to the EnrollmentDate property, as shown in the following example:

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

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }

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

L’attribut DataType sert à spécifier un type de données qui est plus spécifique que le type intrinsèque de la base de données.The DataType attribute is used to specify a data type that's more specific than the database intrinsic type. Dans le cas présent, nous voulons uniquement effectuer le suivi de la date, pas de la date et de l’heure.In this case we only want to keep track of the date, not the date and time. L’énumération DataType fournit de nombreux types de données, tels que Date, Time, PhoneNumber, Currency, EmailAddress, etc.The DataType Enumeration provides for many data types, such as Date, Time, PhoneNumber, Currency, EmailAddress, and more. L’attribut DataType peut également permettre à l’application de fournir automatiquement des fonctionnalités propres au type.The DataType attribute can also enable the application to automatically provide type-specific features. Par exemple, vous pouvez créer un lien mailto: pour DataType.EmailAddress, et vous pouvez fournir un sélecteur de date pour DataType.Date dans les navigateurs qui prennent en charge HTML5.For example, a mailto: link can be created for DataType.EmailAddress, and a date selector can be provided for DataType.Date in browsers that support HTML5. L’attribut DataType émet des attributs HTML 5 data- compréhensibles par les navigateurs HTML 5.The DataType attribute emits HTML 5 data- (pronounced data dash) attributes that HTML 5 browsers can understand. Les attributs DataType ne fournissent aucune validation.The DataType attributes don't provide any validation.

DataType.Date ne spécifie pas le format de la date qui s’affiche.DataType.Date doesn't specify the format of the date that's displayed. Par défaut, le champ de données est affiché conformément aux formats par défaut basés sur l’objet CultureInfo du serveur.By default, the data field is displayed according to the default formats based on the server's CultureInfo.

L’attribut DisplayFormat est utilisé pour spécifier explicitement le format de date :The DisplayFormat attribute is used to explicitly specify the date format:

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

Le paramètre ApplyFormatInEditMode indique que la mise en forme doit également être appliquée quand la valeur est affichée dans une zone de texte à des fins de modification.The ApplyFormatInEditMode setting specifies that the formatting should also be applied when the value is displayed in a text box for editing. (Ceci peut ne pas être souhaitable pour certains champs ; par exemple, pour les valeurs monétaires, vous ne souhaiterez peut-être pas que le symbole monétaire figure dans la zone de texte.)(You might not want that for some fields -- for example, for currency values, you might not want the currency symbol in the text box for editing.)

Vous pouvez utiliser l’attribut DisplayFormat seul, mais il est généralement judicieux d’utiliser également l’attribut DataType.You can use the DisplayFormat attribute by itself, but it's generally a good idea to use the DataType attribute also. L’attribut DataType donne la sémantique des données au lieu d’expliquer comment les afficher à l’écran. Il présente, par ailleurs, les avantages suivants, dont vous ne bénéficiez pas avec DisplayFormat :The DataType attribute conveys the semantics of the data as opposed to how to render it on a screen, and provides the following benefits that you don't get with DisplayFormat:

  • Le navigateur peut activer des fonctionnalités HTML5 (par exemple pour afficher un contrôle de calendrier, le symbole monétaire correspondant aux paramètres régionaux, des liens de messagerie, une certaine validation des entrées côté client, etc.).The browser can enable HTML5 features (for example to show a calendar control, the locale-appropriate currency symbol, email links, some client-side input validation, etc.).

  • Par défaut, le navigateur affiche les données à l’aide du format correspondant à vos paramètres régionaux.By default, the browser will render data using the correct format based on your locale.

Pour plus d’informations, consultez la documentation relative au tag helper <input>.For more information, see the <input> tag helper documentation.

Exécutez l’application, accédez à la page d’index des étudiants et notez que les heures ne sont plus affichées pour les dates d’inscription.Run the app, go to the Students Index page and notice that times are no longer displayed for the enrollment dates. La même chose est vraie pour toute vue qui utilise le modèle Student.The same will be true for any view that uses the Student model.

Page d’index des étudiants affichant les dates sans les heures

Attribut StringLengthThe StringLength attribute

Vous pouvez également spécifier les règles de validation de données et les messages d’erreur de validation à l’aide d’attributs.You can also specify data validation rules and validation error messages using attributes. L’attribut StringLength définit la longueur maximale dans la base de données et assure la validation côté client et côté serveur pour ASP.NET Core MVC.The StringLength attribute sets the maximum length in the database and provides client side and server side validation for ASP.NET Core MVC. Vous pouvez également spécifier la longueur de chaîne minimale dans cet attribut, mais la valeur minimale n’a aucun impact sur le schéma de base de données.You can also specify the minimum string length in this attribute, but the minimum value has no impact on the database schema.

Supposons que vous voulez garantir que les utilisateurs n’entrent pas plus de 50 caractères pour un nom.Suppose you want to ensure that users don't enter more than 50 characters for a name. Pour ajouter cette limitation, ajoutez des attributs StringLength aux propriétés LastName et FirstMidName, comme indiqué dans l’exemple suivant :To add this limitation, add StringLength attributes to the LastName and FirstMidName properties, as shown in the following example:

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

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [StringLength(50)]
        public string LastName { get; set; }
        [StringLength(50)]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }

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

L’attribut StringLength n’empêche pas un utilisateur d’entrer un espace blanc comme nom.The StringLength attribute won't prevent a user from entering white space for a name. Vous pouvez utiliser l’attribut RegularExpression pour appliquer des restrictions à l’entrée.You can use the RegularExpression attribute to apply restrictions to the input. Par exemple, le code suivant exige que le premier caractère soit en majuscule et que les autres caractères soient alphabétiques :For example, the following code requires the first character to be upper case and the remaining characters to be alphabetical:

[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]

L’attribut MaxLength fournit des fonctionnalités similaires à l’attribut StringLength, mais n’assure pas la validation côté client.The MaxLength attribute provides functionality similar to the StringLength attribute but doesn't provide client side validation.

Le modèle de base de données a maintenant changé d’une manière qui nécessite la modification du schéma de base de données.The database model has now changed in a way that requires a change in the database schema. Vous allez utiliser des migrations pour mettre à jour le schéma sans perdre les données que vous avez éventuellement ajoutées à la base de données via l’interface utilisateur de l’application.You'll use migrations to update the schema without losing any data that you may have added to the database by using the application UI.

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 les commandes suivantes :Then open the command window in the project folder and enter the following commands:

dotnet ef migrations add MaxLengthOnNames
dotnet ef database update

La commande migrations add vous avertit qu’une perte de données peut se produire, car la modification raccourcit la longueur maximale de deux colonnes.The migrations add command warns that data loss may occur, because the change makes the maximum length shorter for two columns. Migrations crée un fichier nommé <timeStamp>_MaxLengthOnNames.cs.Migrations creates a file named <timeStamp>_MaxLengthOnNames.cs. Ce fichier contient du code dans la méthode Up qui met à jour la base de données pour qu’elle corresponde au modèle de données actuel.This file contains code in the Up method that will update the database to match the current data model. La commande database update a exécuté ce code.The database update command ran that code.

L’horodatage utilisé comme préfixe du nom de fichier migrations est utilisé par Entity Framework pour ordonner les migrations.The timestamp prefixed to the migrations file name is used by Entity Framework to order the migrations. Vous pouvez créer plusieurs migrations avant d’exécuter la commande de mise à jour de base de données, puis toutes les migrations sont appliquées dans l’ordre où elles ont été créées.You can create multiple migrations before running the update-database command, and then all of the migrations are applied in the order in which they were created.

Exécutez l’application, sélectionnez l’onglet Students, cliquez sur Create New et essayez d’entrer un nom de plus de 50 caractères.Run the app, select the Students tab, click Create New, and try to enter either name longer than 50 characters. L’application doit empêcher cette opération.The application should prevent you from doing this.

Attribut ColumnThe Column attribute

Vous pouvez également utiliser des attributs pour contrôler la façon dont les classes et les propriétés sont mappées à la base de données.You can also use attributes to control how your classes and properties are mapped to the database. Supposons que vous aviez utilisé le nom FirstMidName pour le champ de prénom, car le champ peut également contenir un deuxième prénom.Suppose you had used the name FirstMidName for the first-name field because the field might also contain a middle name. Mais vous souhaitez que la colonne de base de données soit nommée FirstName, car les utilisateurs qui écriront des requêtes ad-hoc par rapport à la base de données sont habitués à ce nom.But you want the database column to be named FirstName, because users who will be writing ad-hoc queries against the database are accustomed to that name. Pour effectuer ce mappage, vous pouvez utiliser l’attribut Column.To make this mapping, you can use the Column attribute.

L’attribut Column spécifie que lorsque la base de données sera créée, la colonne de la table Student qui est mappée sur la propriété FirstMidName sera nommée FirstName.The Column attribute specifies that when the database is created, the column of the Student table that maps to the FirstMidName property will be named FirstName. En d’autres termes, lorsque votre code fait référence à Student.FirstMidName, les données proviennent de la colonne FirstName de la table Student ou y sont mises à jour.In other words, when your code refers to Student.FirstMidName, the data will come from or be updated in the FirstName column of the Student table. Si vous ne nommez pas les colonnes, elles obtiennent le nom de la propriété.If you don't specify column names, they're given the same name as the property name.

Dans le fichier Student.cs, ajoutez une instruction using pour System.ComponentModel.DataAnnotations.Schema et ajoutez l’attribut de nom de colonne à la propriété FirstMidName, comme indiqué dans le code en surbrillance suivant :In the Student.cs file, add a using statement for System.ComponentModel.DataAnnotations.Schema and add the column name attribute to the FirstMidName property, as shown in the following highlighted code:

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

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [StringLength(50)]
        public string LastName { get; set; }
        [StringLength(50)]
        [Column("FirstName")]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }

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

L’ajout de l’attribut Column change le modèle sur lequel repose SchoolContext, donc il ne correspond pas à la base de données.The addition of the Column attribute changes the model backing the SchoolContext, so it won't match the database.

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 les commandes suivantes pour créer une autre migration :Then open the command window in the project folder and enter the following commands to create another migration:

dotnet ef migrations add ColumnFirstName
dotnet ef database update

Dans l’Explorateur d’objets SQL Server, ouvrez le concepteur de tables Student en double-cliquant sur la table Student.In SQL Server Object Explorer, open the Student table designer by double-clicking the Student table.

Table Students dans SSOX après les migrations

Avant d’appliquer les deux premières migrations, les colonnes de nom étaient de type nvarchar(MAX).Before you applied the first two migrations, the name columns were of type nvarchar(MAX). Elles sont maintenant de type nvarchar(50) et le nom de colonne FirstMidName a été remplacé par FirstName.They're now nvarchar(50) and the column name has changed from FirstMidName to FirstName.

Notes

Si vous essayez de compiler avant d’avoir fini de créer toutes les classes d’entité dans les sections suivantes, vous pouvez obtenir des erreurs de compilation.If you try to compile before you finish creating all of the entity classes in the following sections, you might get compiler errors.

Apporter des modifications à l’entité StudentChanges to Student entity

Entité Student

Dans Models/Student.cs, remplacez le code que vous avez ajouté précédemment par le code suivant.In Models/Student.cs, replace the code you added earlier with the following code. Les modifications apparaissent en surbrillance.The changes are highlighted.

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

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [Required]
        [StringLength(50)]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
        [Required]
        [StringLength(50)]
        [Column("FirstName")]
        [Display(Name = "First Name")]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Enrollment Date")]
        public DateTime EnrollmentDate { get; set; }
        [Display(Name = "Full Name")]
        public string FullName
        {
            get
            {
                return LastName + ", " + FirstMidName;
            }
        }

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

Attribut RequiredThe Required attribute

L’attribut Required fait des propriétés de nom des champs obligatoires.The Required attribute makes the name properties required fields. L’attribut Required n’est pas requis pour les types non nullables tels que les types valeur (DateTime, int, double, float, etc.).The Required attribute isn't needed for non-nullable types such as value types (DateTime, int, double, float, etc.). Les types qui n’acceptent pas les valeurs Null sont traités automatiquement comme des champs requis.Types that can't be null are automatically treated as required fields.

Vous pouvez supprimer l’attribut Required et le remplacer par un paramètre de longueur minimale pour l’attribut StringLength :You could remove the Required attribute and replace it with a minimum length parameter for the StringLength attribute:

[Display(Name = "Last Name")]
[StringLength(50, MinimumLength=1)]
public string LastName { get; set; }

Attribut DisplayThe Display attribute

L’attribut Display spécifie que la légende pour les zones de texte doit être « First Name », « Last Name », « Full Name » et « Enrollment Date », au lieu du nom de propriété dans chaque instance (qui n’a pas d’espace pour séparer les mots).The Display attribute specifies that the caption for the text boxes should be "First Name", "Last Name", "Full Name", and "Enrollment Date" instead of the property name in each instance (which has no space dividing the words).

Propriété calculée FullNameThe FullName calculated property

FullName est une propriété calculée qui retourne une valeur créée par concaténation de deux autres propriétés.FullName is a calculated property that returns a value that's created by concatenating two other properties. Par conséquent, elle a uniquement un accesseur get et aucune colonne FullName n’est générée dans la base de données.Therefore it has only a get accessor, and no FullName column will be generated in the database.

Créer une entité InstructorCreate Instructor entity

Entité Instructor

Créez Models/Instructor.cs, en remplaçant le code du modèle par le code suivant :Create Models/Instructor.cs, replacing the template code with the following code:

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

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

        [Required]
        [Display(Name = "Last Name")]
        [StringLength(50)]
        public string LastName { get; set; }

        [Required]
        [Column("FirstName")]
        [Display(Name = "First Name")]
        [StringLength(50)]
        public string FirstMidName { get; set; }

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

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

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

Notez que plusieurs propriétés sont identiques dans les entités Student et Instructor.Notice that several properties are the same in the Student and Instructor entities. Dans le didacticiel Implémentation de l’héritage plus loin dans cette série, vous allez refactoriser ce code pour éliminer la redondance.In the Implementing Inheritance tutorial later in this series, you'll refactor this code to eliminate the redundancy.

Vous pouvez placer plusieurs attributs sur une seule ligne et écrire les attributs HireDate comme suit :You can put multiple attributes on one line, so you could also write the HireDate attributes as follows:

[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

Propriétés de navigation CourseAssignments et OfficeAssignmentThe CourseAssignments and OfficeAssignment navigation properties

Les propriétés CourseAssignments et OfficeAssignment sont des propriétés de navigation.The CourseAssignments and OfficeAssignment properties are navigation properties.

Un formateur peut animer un nombre quelconque de cours, de sorte que CourseAssignments est défini comme une collection.An instructor can teach any number of courses, so CourseAssignments is defined as a collection.

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

Si une propriété de navigation peut contenir plusieurs entités, son type doit être une liste dans laquelle les entrées peuvent être ajoutées, supprimées et mises à jour.If a navigation property can hold multiple entities, its type must be a list in which entries can be added, deleted, and updated. Vous pouvez spécifier ICollection<T> ou un type tel que List<T> ou HashSet<T>.You can specify ICollection<T> or a type such as List<T> or HashSet<T>. Si vous spécifiez ICollection<T>, EF crée une collection HashSet<T> par défaut.If you specify ICollection<T>, EF creates a HashSet<T> collection by default.

La raison pour laquelle ce sont des entités CourseAssignment est expliquée ci-dessous dans la section sur les relations plusieurs-à-plusieurs.The reason why these are CourseAssignment entities is explained below in the section about many-to-many relationships.

Les règles d’entreprise de Contoso University stipulent qu’un formateur peut avoir au plus un bureau, de sorte que la propriété OfficeAssignment contient une seule entité OfficeAssignment (qui peut être null si aucun bureau n’est affecté).Contoso University business rules state that an instructor can only have at most one office, so the OfficeAssignment property holds a single OfficeAssignment entity (which may be null if no office is assigned).

public OfficeAssignment OfficeAssignment { get; set; }

Créer une entité OfficeAssignmentCreate OfficeAssignment entity

Entité OfficeAssignment

Créez Models/OfficeAssignment.cs avec le code suivant :Create Models/OfficeAssignment.cs with the following code:

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

namespace ContosoUniversity.Models
{
    public class OfficeAssignment
    {
        [Key]
        public int InstructorID { get; set; }
        [StringLength(50)]
        [Display(Name = "Office Location")]
        public string Location { get; set; }

        public Instructor Instructor { get; set; }
    }
}

Attribut KeyThe Key attribute

Il existe une relation un-à-zéro-ou-un entre les entités Instructor et OfficeAssignment.There's a one-to-zero-or-one relationship between the Instructor and the OfficeAssignment entities. Une affectation de bureau existe uniquement en relation avec le formateur auquel elle est affectée. Par conséquent, sa clé primaire est également sa clé étrangère pour l’entité Instructor.An office assignment only exists in relation to the instructor it's assigned to, and therefore its primary key is also its foreign key to the Instructor entity. Mais Entity Framework ne peut pas reconnaître automatiquement InstructorID comme clé primaire de cette entité, car son nom ne suit pas la convention de nommage d’ID ou de classnameID.But the Entity Framework can't automatically recognize InstructorID as the primary key of this entity because its name doesn't follow the ID or classnameID naming convention. Par conséquent, l’attribut Key est utilisé pour l’identifier comme clé :Therefore, the Key attribute is used to identify it as the key:

[Key]
public int InstructorID { get; set; }

Vous pouvez également utiliser l’attribut Key si l’entité a sa propre clé primaire, mais que vous souhaitez nommer la propriété autrement que classnameID ou ID.You can also use the Key attribute if the entity does have its own primary key but you want to name the property something other than classnameID or ID.

Par défaut, EF traite la clé comme n’étant pas générée par la base de données, car la colonne est utilisée pour une relation d’identification.By default, EF treats the key as non-database-generated because the column is for an identifying relationship.

Propriété de navigation du formateurThe Instructor navigation property

L’entité Instructor a une propriété de navigation OfficeAssignment nullable (parce qu’un formateur n’a peut-être pas d’affectation de bureau) et l’entité OfficeAssignment a une propriété de navigation Instructor non nullable (comme une affectation de bureau ne peut pas exister sans formateur, InstructorID est non nullable).The Instructor entity has a nullable OfficeAssignment navigation property (because an instructor might not have an office assignment), and the OfficeAssignment entity has a non-nullable Instructor navigation property (because an office assignment can't exist without an instructor -- InstructorID is non-nullable). Lorsqu’une entité Instructor a une entité OfficeAssignment associée, chaque entité a une référence à l’autre dans sa propriété de navigation.When an Instructor entity has a related OfficeAssignment entity, each entity will have a reference to the other one in its navigation property.

Vous pouvez placer un attribut [Required] sur la propriété de navigation du formateur pour spécifier qu’il doit y avoir un formateur associé, mais vous n’êtes pas obligé de le faire, car la clé étrangère InstructorID (qui est également la clé pour cette table) est non nullable.You could put a [Required] attribute on the Instructor navigation property to specify that there must be a related instructor, but you don't have to do that because the InstructorID foreign key (which is also the key to this table) is non-nullable.

Modifier l’entité CourseModify Course entity

Entité Course

Dans Models/Course.cs, remplacez le code que vous avez ajouté précédemment par le code suivant.In Models/Course.cs, replace the code you added earlier with the following code. Les modifications apparaissent en surbrillance.The changes are highlighted.

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

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        [Display(Name = "Number")]
        public int CourseID { get; set; }

        [StringLength(50, MinimumLength = 3)]
        public string Title { get; set; }

        [Range(0, 5)]
        public int Credits { get; set; }

        public int DepartmentID { get; set; }

        public Department Department { get; set; }
        public ICollection<Enrollment> Enrollments { get; set; }
        public ICollection<CourseAssignment> CourseAssignments { get; set; }
    }
}

L’entité de cours a une propriété de clé étrangère DepartmentID qui pointe sur l’entité Department associée et elle a une propriété de navigation Department.The course entity has a foreign key property DepartmentID which points to the related Department entity and it has a Department navigation property.

Entity Framework ne vous demande pas d’ajouter une propriété de clé étrangère à votre modèle de données lorsque vous avez une propriété de navigation pour une entité associée.The Entity Framework doesn't require you to add a foreign key property to your data model when you have a navigation property for a related entity. EF crée automatiquement des clés étrangères dans la base de données partout où elles sont nécessaires et crée des propriétés fantôme pour elles.EF automatically creates foreign keys in the database wherever they're needed and creates shadow properties for them. Mais le fait d’avoir la clé étrangère dans le modèle de données peut rendre les mises à jour plus simples et plus efficaces.But having the foreign key in the data model can make updates simpler and more efficient. Par exemple, lorsque vous récupérez une entité de cours à modifier, l’entité Department a la valeur Null si vous ne la chargez pas. Par conséquent, lorsque vous mettez à jour l’entité de cours, vous devriez tout d’abord récupérer l’entité Department.For example, when you fetch a course entity to edit, the Department entity is null if you don't load it, so when you update the course entity, you would have to first fetch the Department entity. Lorsque la propriété de clé étrangère DepartmentID est incluse dans le modèle de données, vous n’avez pas besoin de récupérer l’entité Department avant de mettre à jour.When the foreign key property DepartmentID is included in the data model, you don't need to fetch the Department entity before you update.

Attribut DatabaseGeneratedThe DatabaseGenerated attribute

L’attribut DatabaseGenerated avec le paramètre None sur la propriété CourseID spécifie que les valeurs de clé primaire sont fournies par l’utilisateur au lieu d’être générées par la base de données.The DatabaseGenerated attribute with the None parameter on the CourseID property specifies that primary key values are provided by the user rather than generated by the database.

[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

Par défaut, Entity Framework suppose que les valeurs de clé primaire sont générées par la base de données.By default, Entity Framework assumes that primary key values are generated by the database. C’est ce que vous souhaitez dans la plupart des scénarios.That's what you want in most scenarios. Toutefois, pour les entités Course, vous allez utiliser un numéro de cours spécifié par l’utilisateur comme une série de 1000 pour un département, une série de 2000 pour un autre département, etc.However, for Course entities, you'll use a user-specified course number such as a 1000 series for one department, a 2000 series for another department, and so on.

L’attribut DatabaseGenerated peut également être utilisé pour générer des valeurs par défaut, comme dans le cas des colonnes de base de données utilisées pour enregistrer la date à laquelle une ligne a été créée ou mise à jour.The DatabaseGenerated attribute can also be used to generate default values, as in the case of database columns used to record the date a row was created or updated. Pour plus d’informations, consultez Propriétés générées.For more information, see Generated Properties.

Propriétés de clé étrangère et de navigationForeign key and navigation properties

Les propriétés de clé étrangère et les propriétés de navigation dans l’entité Course reflètent les relations suivantes :The foreign key properties and navigation properties in the Course entity reflect the following relationships:

Un cours est affecté à un seul département, donc il existe une clé étrangère DepartmentID et une propriété de navigation Department pour les raisons mentionnées ci-dessus.A course is assigned to one department, so there's a DepartmentID foreign key and a Department navigation property for the reasons mentioned above.

public int DepartmentID { get; set; }
public Department Department { get; set; }

Un cours peut avoir un nombre quelconque d’étudiants inscrits, si bien que la propriété de navigation Enrollments est une collection :A course can have any number of students enrolled in it, so the Enrollments navigation property is a collection:

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

Un cours peut être animé par plusieurs formateurs, si bien que la propriété de navigation CourseAssignments est une collection (le type CourseAssignment est expliqué ultérieurement) :A course may be taught by multiple instructors, so the CourseAssignments navigation property is a collection (the type CourseAssignment is explained later):

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

Créer l’entité DepartmentCreate Department entity

Entité Department

Créez Models/Department.cs avec le code suivant :Create Models/Department.cs with the following code:

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

namespace ContosoUniversity.Models
{
    public class Department
    {
        public int DepartmentID { get; set; }

        [StringLength(50, MinimumLength = 3)]
        public string Name { get; set; }

        [DataType(DataType.Currency)]
        [Column(TypeName = "money")]
        public decimal Budget { get; set; }

        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Start Date")]
        public DateTime StartDate { get; set; }

        public int? InstructorID { get; set; }

        public Instructor Administrator { get; set; }
        public ICollection<Course> Courses { get; set; }
    }
}

Attribut ColumnThe Column attribute

Précédemment, vous avez utilisé l’attribut Column pour changer le mappage de noms de colonne.Earlier you used the Column attribute to change column name mapping. Dans le code de l’entité Department, l’attribut Column sert à modifier le mappage des types de données SQL afin que la colonne soit définie à l’aide du type monétaire (money) SQL Server dans la base de données :In the code for the Department entity, the Column attribute is being used to change SQL data type mapping so that the column will be defined using the SQL Server money type in the database:

[Column(TypeName="money")]
public decimal Budget { get; set; }

Le mappage de colonnes n’est généralement pas nécessaire, car Entity Framework choisit le type de données SQL Server approprié en fonction du type CLR que vous définissez pour la propriété.Column mapping is generally not required, because the Entity Framework chooses the appropriate SQL Server data type based on the CLR type that you define for the property. Le type CLR decimal est mappé à un type SQL Server decimal.The CLR decimal type maps to a SQL Server decimal type. Toutefois, dans ce cas, vous savez que la colonne contiendra des montants en devise et que le type de données monétaire est plus approprié pour cela.But in this case you know that the column will be holding currency amounts, and the money data type is more appropriate for that.

Propriétés de clé étrangère et de navigationForeign key and navigation properties

Les propriétés de clé étrangère et de navigation reflètent les relations suivantes :The foreign key and navigation properties reflect the following relationships:

Un département peut ou non avoir un administrateur, et un administrateur est toujours un formateur.A department may or may not have an administrator, and an administrator is always an instructor. Par conséquent, la propriété InstructorID est incluse en tant que clé étrangère à l’entité Instructor, et un point d’interrogation est ajouté après la désignation du type int pour marquer la propriété comme nullable.Therefore the InstructorID property is included as the foreign key to the Instructor entity, and a question mark is added after the int type designation to mark the property as nullable. La propriété de navigation est nommée Administrator, mais elle contient une entité Instructor :The navigation property is named Administrator but holds an Instructor entity:

public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }

Un département peut avoir de nombreux cours, si bien qu’il existe une propriété de navigation Courses :A department may have many courses, so there's a Courses navigation property:

public ICollection<Course> Courses { get; set; }

Notes

Par convention, Entity Framework permet la suppression en cascade pour les clés étrangères non nullables et pour les relations plusieurs à plusieurs.By convention, the Entity Framework enables cascade delete for non-nullable foreign keys and for many-to-many relationships. Cela peut entraîner des règles de suppression en cascade circulaires, qui provoqueront une exception lorsque vous essaierez d’ajouter une migration.This can result in circular cascade delete rules, which will cause an exception when you try to add a migration. Par exemple, si vous n’avez pas défini la propriété Department.InstructorID comme nullable, EF configure une règle de suppression en cascade pour supprimer le département lorsque vous supprimez le formateur, ce qui n’est pas ce que vous voulez.For example, if you didn't define the Department.InstructorID property as nullable, EF would configure a cascade delete rule to delete the department when you delete the instructor, which isn't what you want to have happen. Si vos règles d’entreprise exigent que la propriété InstructorID soit non nullable, vous devez utiliser l’instruction d’API Fluent suivante pour désactiver la suppression en cascade sur la relation :If your business rules required the InstructorID property to be non-nullable, you would have to use the following fluent API statement to disable cascade delete on the relationship:

modelBuilder.Entity<Department>()
   .HasOne(d => d.Administrator)
   .WithMany()
   .OnDelete(DeleteBehavior.Restrict)

Modifier l’entité EnrollmentModify Enrollment entity

Entité Enrollment

Dans Models/Enrollment.cs, remplacez le code que vous avez ajouté précédemment par le code suivant :In Models/Enrollment.cs, replace the code you added earlier with the following code:

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

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

Propriétés de clé étrangère et de navigationForeign key and navigation properties

Les propriétés de clé étrangère et de navigation reflètent les relations suivantes :The foreign key properties and navigation properties reflect the following relationships:

Un enregistrement d’inscription est utilisé pour un cours unique, si bien qu’il existe une propriété de clé étrangère CourseID et une propriété de navigation Course :An enrollment record is for a single course, so there's a CourseID foreign key property and a Course navigation property:

public int CourseID { get; set; }
public Course Course { get; set; }

Un enregistrement d’inscription est utilisé pour un étudiant unique, si bien qu’il existe une propriété de clé étrangère StudentID et une propriété de navigation Student :An enrollment record is for a single student, so there's a StudentID foreign key property and a Student navigation property:

public int StudentID { get; set; }
public Student Student { get; set; }

Relations plusieurs-à-plusieursMany-to-Many relationships

Il existe une relation plusieurs-à-plusieurs entre les entités Student et Course, et l’entité Enrollment fonctionne comme une table de jointure plusieurs-à-plusieurs avec une charge utile dans la base de données.There's a many-to-many relationship between the Student and Course entities, and the Enrollment entity functions as a many-to-many join table with payload in the database. « Avec une charge utile » signifie que la table Enrollment contient des données supplémentaires en plus des clés étrangères pour les tables jointes (dans ce cas, une clé primaire et une propriété Grade)."With payload" means that the Enrollment table contains additional data besides foreign keys for the joined tables (in this case, a primary key and a Grade property).

L’illustration suivante montre à quoi ressemblent ces relations dans un diagramme d’entité.The following illustration shows what these relationships look like in an entity diagram. (Ce diagramme a été généré à l’aide d’Entity Framework Power Tools pour EF 6.x ; la création du diagramme ne fait pas partie de ce didacticiel, elle est uniquement utilisée ici à titre d’illustration.)(This diagram was generated using the Entity Framework Power Tools for EF 6.x; creating the diagram isn't part of the tutorial, it's just being used here as an illustration.)

Relation plusieurs-à-plusieurs Student-Course

Chaque ligne de relation comporte un 1 à une extrémité et un astérisque (*) à l’autre, ce qui indique une relation un-à-plusieurs.Each relationship line has a 1 at one end and an asterisk (*) at the other, indicating a one-to-many relationship.

Si la table Enrollment n’incluait pas d’informations de notes, elle aurait uniquement besoin de contenir les deux clés étrangères CourseID et StudentID.If the Enrollment table didn't include grade information, it would only need to contain the two foreign keys CourseID and StudentID. Dans ce cas, ce serait une table de jointure plusieurs-à-plusieurs sans charge utile (ou une table de jointure pure) dans la base de données.In that case, it would be a many-to-many join table without payload (or a pure join table) in the database. Les entités Instructor and Course ont ce type de relation plusieurs-à-plusieurs, et l’étape suivante consiste à créer une classe d’entité qui fonctionnera comme une table de jointure sans charge utile.The Instructor and Course entities have that kind of many-to-many relationship, and your next step is to create an entity class to function as a join table without payload.

(EF 6.x prend en charge les tables de jointure implicites pour les relations plusieurs-à-plusieurs, mais EF Core ne le fait pas.(EF 6.x supports implicit join tables for many-to-many relationships, but EF Core doesn't. Pour plus d’informations, consultez la discussion dans le dépôt GitHub EF Core.)For more information, see the discussion in the EF Core GitHub repository.)

Entité CourseAssignmentThe CourseAssignment entity

Entité CourseAssignment

Créez Models/CourseAssignment.cs avec le code suivant :Create Models/CourseAssignment.cs with the following code:

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

namespace ContosoUniversity.Models
{
    public class CourseAssignment
    {
        public int InstructorID { get; set; }
        public int CourseID { get; set; }
        public Instructor Instructor { get; set; }
        public Course Course { get; set; }
    }
}

Noms des entités de jointureJoin entity names

Une table de jointure est requise dans la base de données pour la relation plusieurs-à-plusieurs entre formateurs et cours, et elle doit être représentée par un jeu d’entités.A join table is required in the database for the Instructor-to-Courses many-to-many relationship, and it has to be represented by an entity set. Il est courant de nommer une entité de jointure EntityName1EntityName2, ce qui donnerait dans ce cas CourseInstructor.It's common to name a join entity EntityName1EntityName2, which in this case would be CourseInstructor. Toutefois, nous vous recommandons de choisir un nom qui décrit la relation.However, we recommend that you choose a name that describes the relationship. Les modèles de données sont simples au départ, puis croissent, avec des jointures sans charge utile qui obtiennent souvent des charges utiles plus tard.Data models start out simple and grow, with no-payload joins frequently getting payloads later. Si vous commencez avec un nom d’entité descriptif, vous n’aurez pas à le modifier par la suite.If you start with a descriptive entity name, you won't have to change the name later. Dans l’idéal, l’entité de jointure aura son propre nom (éventuellement un mot unique) naturel dans le domaine d’entreprise.Ideally, the join entity would have its own natural (possibly single word) name in the business domain. Par exemple, les livres et les clients pourraient être liés par le biais d’évaluations.For example, Books and Customers could be linked through Ratings. Pour cette relation, CourseAssignment est un meilleur choix que CourseInstructor.For this relationship, CourseAssignment is a better choice than CourseInstructor.

Clé compositeComposite key

Étant donné que les clés étrangères ne sont pas nullables et qu’elles identifient ensemble de façon unique chaque ligne de la table, une clé primaire distincte n’est pas requise.Since the foreign keys are not nullable and together uniquely identify each row of the table, there's no need for a separate primary key. Les propriétés InstructorID et CourseID doivent fonctionner comme une clé primaire composite.The InstructorID and CourseID properties should function as a composite primary key. La seule façon d’identifier des clés primaires composites pour EF consiste à utiliser l’API Fluent (ce n’est pas possible à l’aide d’attributs).The only way to identify composite primary keys to EF is by using the fluent API (it can't be done by using attributes). Vous allez voir comment configurer la clé primaire composite dans la section suivante.You'll see how to configure the composite primary key in the next section.

La clé composite garantit qu’en ayant plusieurs lignes pour un cours et plusieurs lignes pour un formateur, vous ne puissiez pas avoir plusieurs lignes pour les mêmes formateur et cours.The composite key ensures that while you can have multiple rows for one course, and multiple rows for one instructor, you can't have multiple rows for the same instructor and course. L’entité de jointure Enrollment définit sa propre clé primaire, si bien que les doublons de ce type sont possibles.The Enrollment join entity defines its own primary key, so duplicates of this sort are possible. Pour éviter ces doublons, vous pourriez ajouter un index unique sur les champs de clé étrangère ou configurer Enrollment avec une clé composite primaire similaire à CourseAssignment.To prevent such duplicates, you could add a unique index on the foreign key fields, or configure Enrollment with a primary composite key similar to CourseAssignment. Pour plus d’informations, consultez Index.For more information, see Indexes.

Mettre à jour le contexte de base de donnéesUpdate the database context

Ajoutez le code en surbrillance suivant au fichier Data/SchoolContext.cs :Add the following highlighted code to the Data/SchoolContext.cs file:

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; }

        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<CourseAssignment>()
                .HasKey(c => new { c.CourseID, c.InstructorID });
        }
    }
}

Ce code ajoute les nouvelles entités et configure la clé primaire composite de l’entité CourseAssignment.This code adds the new entities and configures the CourseAssignment entity's composite primary key.

À propos de l’alternative d’API FluentAbout a fluent API alternative

Le code dans la méthode OnModelCreating de la classe DbContext utilise l’API Fluent pour configurer le comportement EF.The code in the OnModelCreating method of the DbContext class uses the fluent API to configure EF behavior. L’API est appelée « fluent », car elle est souvent utilisée pour enchaîner une série d’appels de méthode en une seule instruction, comme dans cet exemple tiré de la documentation d’EF Core :The API is called "fluent" because it's often used by stringing a series of method calls together into a single statement, as in this example from the EF Core documentation:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Url)
        .IsRequired();
}

Dans ce didacticiel, vous utilisez l’API Fluent uniquement pour le mappage de base de données que vous ne pouvez pas faire avec des attributs.In this tutorial, you're using the fluent API only for database mapping that you can't do with attributes. Toutefois, vous pouvez également utiliser l’API Fluent pour spécifier la majorité des règles de mise en forme, de validation et de mappage que vous pouvez spécifier à l’aide d’attributs.However, you can also use the fluent API to specify most of the formatting, validation, and mapping rules that you can do by using attributes. Certains attributs, tels que MinimumLength, ne peuvent pas être appliqués avec l’API Fluent.Some attributes such as MinimumLength can't be applied with the fluent API. Comme mentionné précédemment, MinimumLength ne change pas le schéma, il applique uniquement une règle de validation côté client et côté serveur.As mentioned previously, MinimumLength doesn't change the schema, it only applies a client and server side validation rule.

Certains développeurs préfèrent utiliser exclusivement l’API Fluent afin de conserver des classes d’entité « propres ».Some developers prefer to use the fluent API exclusively so that they can keep their entity classes "clean." Vous pouvez combiner les attributs et l’API Fluent si vous le voulez, et il existe quelques personnalisations qui peuvent être effectuées uniquement à l’aide de l’API Fluent, mais en général la pratique recommandée consiste à choisir l’une de ces deux approches et à l’utiliser constamment, autant que possible.You can mix attributes and fluent API if you want, and there are a few customizations that can only be done by using fluent API, but in general the recommended practice is to choose one of these two approaches and use that consistently as much as possible. Si vous utilisez ces deux approches, notez que partout où il existe un conflit, l’API Fluent a priorité sur les attributs.If you do use both, note that wherever there's a conflict, Fluent API overrides attributes.

Pour plus d’informations sur les attributs et l’API Fluent, consultez Méthodes de configuration.For more information about attributes vs. fluent API, see Methods of configuration.

Diagramme des entités montrant les relationsEntity Diagram Showing Relationships

L’illustration suivante montre le diagramme que les outils Entity Framework Power Tools créent pour le modèle School complet.The following illustration shows the diagram that the Entity Framework Power Tools create for the completed School model.

Diagramme des entités

Outre les lignes de relation un-à-plusieurs (1 à *), vous pouvez voir ici la ligne de relation un-à-zéro-ou-un (1 à 0..1) entre les entités Instructor et OfficeAssignment et la ligne de relation zéro-ou-un-à-plusieurs (0..1 à *) entre les entités Instructor et Department.Besides the one-to-many relationship lines (1 to *), you can see here the one-to-zero-or-one relationship line (1 to 0..1) between the Instructor and OfficeAssignment entities and the zero-or-one-to-many relationship line (0..1 to *) between the Instructor and Department entities.

Remplir la base de données avec des données de testSeed database with test data

Remplacez le code dans le fichier Data/DbInitializer.cs par le code suivant afin de fournir des données initiales pour les nouvelles entités que vous avez créées.Replace the code in the Data/DbInitializer.cs file with the following code in order to provide seed data for the new entities you've created.

using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            //context.Database.EnsureCreated();

            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student { FirstMidName = "Carson",   LastName = "Alexander",
                    EnrollmentDate = DateTime.Parse("2010-09-01") },
                new Student { FirstMidName = "Meredith", LastName = "Alonso",
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Arturo",   LastName = "Anand",
                    EnrollmentDate = DateTime.Parse("2013-09-01") },
                new Student { FirstMidName = "Gytis",    LastName = "Barzdukas",
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Yan",      LastName = "Li",
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Peggy",    LastName = "Justice",
                    EnrollmentDate = DateTime.Parse("2011-09-01") },
                new Student { FirstMidName = "Laura",    LastName = "Norman",
                    EnrollmentDate = DateTime.Parse("2013-09-01") },
                new Student { FirstMidName = "Nino",     LastName = "Olivetto",
                    EnrollmentDate = DateTime.Parse("2005-09-01") }
            };

            foreach (Student s in students)
            {
                context.Students.Add(s);
            }
            context.SaveChanges();

            var instructors = new Instructor[]
            {
                new Instructor { FirstMidName = "Kim",     LastName = "Abercrombie",
                    HireDate = DateTime.Parse("1995-03-11") },
                new Instructor { FirstMidName = "Fadi",    LastName = "Fakhouri",
                    HireDate = DateTime.Parse("2002-07-06") },
                new Instructor { FirstMidName = "Roger",   LastName = "Harui",
                    HireDate = DateTime.Parse("1998-07-01") },
                new Instructor { FirstMidName = "Candace", LastName = "Kapoor",
                    HireDate = DateTime.Parse("2001-01-15") },
                new Instructor { FirstMidName = "Roger",   LastName = "Zheng",
                    HireDate = DateTime.Parse("2004-02-12") }
            };

            foreach (Instructor i in instructors)
            {
                context.Instructors.Add(i);
            }
            context.SaveChanges();

            var departments = new Department[]
            {
                new Department { Name = "English",     Budget = 350000,
                    StartDate = DateTime.Parse("2007-09-01"),
                    InstructorID  = instructors.Single( i => i.LastName == "Abercrombie").ID },
                new Department { Name = "Mathematics", Budget = 100000,
                    StartDate = DateTime.Parse("2007-09-01"),
                    InstructorID  = instructors.Single( i => i.LastName == "Fakhouri").ID },
                new Department { Name = "Engineering", Budget = 350000,
                    StartDate = DateTime.Parse("2007-09-01"),
                    InstructorID  = instructors.Single( i => i.LastName == "Harui").ID },
                new Department { Name = "Economics",   Budget = 100000,
                    StartDate = DateTime.Parse("2007-09-01"),
                    InstructorID  = instructors.Single( i => i.LastName == "Kapoor").ID }
            };

            foreach (Department d in departments)
            {
                context.Departments.Add(d);
            }
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course {CourseID = 1050, Title = "Chemistry",      Credits = 3,
                    DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID
                },
                new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
                    DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
                },
                new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
                    DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
                },
                new Course {CourseID = 1045, Title = "Calculus",       Credits = 4,
                    DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
                },
                new Course {CourseID = 3141, Title = "Trigonometry",   Credits = 4,
                    DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
                },
                new Course {CourseID = 2021, Title = "Composition",    Credits = 3,
                    DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
                },
                new Course {CourseID = 2042, Title = "Literature",     Credits = 4,
                    DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
                },
            };

            foreach (Course c in courses)
            {
                context.Courses.Add(c);
            }
            context.SaveChanges();

            var officeAssignments = new OfficeAssignment[]
            {
                new OfficeAssignment {
                    InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID,
                    Location = "Smith 17" },
                new OfficeAssignment {
                    InstructorID = instructors.Single( i => i.LastName == "Harui").ID,
                    Location = "Gowan 27" },
                new OfficeAssignment {
                    InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID,
                    Location = "Thompson 304" },
            };

            foreach (OfficeAssignment o in officeAssignments)
            {
                context.OfficeAssignments.Add(o);
            }
            context.SaveChanges();

            var courseInstructors = new CourseAssignment[]
            {
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Kapoor").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Harui").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Fakhouri").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Harui").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
                    },
                new CourseAssignment {
                    CourseID = courses.Single(c => c.Title == "Literature" ).CourseID,
                    InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
                    },
            };

            foreach (CourseAssignment ci in courseInstructors)
            {
                context.CourseAssignments.Add(ci);
            }
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alexander").ID,
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
                    Grade = Grade.A
                },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alexander").ID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
                    Grade = Grade.C
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alexander").ID,
                    CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                        StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                        StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Anand").ID,
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Anand").ID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
                    Grade = Grade.B
                    },
                new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
                    CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Li").ID,
                    CourseID = courses.Single(c => c.Title == "Composition").CourseID,
                    Grade = Grade.B
                    },
                    new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Justice").ID,
                    CourseID = courses.Single(c => c.Title == "Literature").CourseID,
                    Grade = Grade.B
                    }
            };

            foreach (Enrollment e in enrollments)
            {
                var enrollmentInDataBase = context.Enrollments.Where(
                    s =>
                            s.Student.ID == e.StudentID &&
                            s.Course.CourseID == e.CourseID).SingleOrDefault();
                if (enrollmentInDataBase == null)
                {
                    context.Enrollments.Add(e);
                }
            }
            context.SaveChanges();
        }
    }
}

Comme vous l’avez vu dans le premier didacticiel, la majeure partie de ce code crée simplement de nouveaux objets d’entité et charge des exemples de données dans les propriétés requises pour les tests.As you saw in the first tutorial, most of this code simply creates new entity objects and loads sample data into properties as required for testing. Notez la façon dont les relations plusieurs à plusieurs sont gérées : le code crée des relations en créant des entités dans les jeux d’entités de jointure Enrollments et CourseAssignment.Notice how the many-to-many relationships are handled: the code creates relationships by creating entities in the Enrollments and CourseAssignment join entity sets.

Ajouter une migrationAdd a migration

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 migrations add (n’exécutez pas encore la commande de mise à jour de base de données) :Then open the command window in the project folder and enter the migrations add command (don't do the update-database command yet):

dotnet ef migrations add ComplexDataModel

Vous obtenez un avertissement concernant une perte possible de données.You get a warning about possible data loss.

An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.
Done. To undo this action, use 'ef migrations remove'

Si vous tentiez d’exécuter la commande database update à ce stade (ne le faites pas encore), vous obtiendriez l’erreur suivante :If you tried to run the database update command at this point (don't do it yet), you would get the following error:

L’instruction ALTER TABLE est en conflit avec la contrainte FOREIGN KEY « FK_dbo.Course_dbo.Department_DepartmentID ».The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "FK_dbo.Course_dbo.Department_DepartmentID". Le conflit s’est produit dans la base de données « ContosoUniversity », table « dbo.Department », colonne « DepartmentID ».The conflict occurred in database "ContosoUniversity", table "dbo.Department", column 'DepartmentID'.

Parfois, lorsque vous exécutez des migrations avec des données existantes, vous devez insérer des données stub dans la base de données pour répondre aux contraintes de clé étrangère.Sometimes when you execute migrations with existing data, you need to insert stub data into the database to satisfy foreign key constraints. Le code généré dans la méthode Up ajoute une clé étrangère DepartmentID non nullable à la table Course.The generated code in the Up method adds a non-nullable DepartmentID foreign key to the Course table. S’il existe déjà des lignes dans la table Course lorsque le code s’exécute, l’opération AddColumn échoue car SQL Server ne sait pas quelle valeur placer dans la colonne qui ne peut pas être null.If there are already rows in the Course table when the code runs, the AddColumn operation fails because SQL Server doesn't know what value to put in the column that can't be null. Pour ce didacticiel, vous allez exécuter la migration sur une nouvelle base de données. Toutefois, dans une application de production, vous devriez faire en sorte que la migration traite les données existantes, si bien que les instructions suivantes montrent un exemple de la procédure à suivre pour ce faire.For this tutorial you'll run the migration on a new database, but in a production application you'd have to make the migration handle existing data, so the following directions show an example of how to do that.

Pour faire en sorte que cette migration fonctionne avec les données existantes, vous devez modifier le code pour attribuer à la nouvelle colonne une valeur par défaut et créer un département stub nommé « Temp » qui agira en tant que département par défaut.To make this migration work with existing data you have to change the code to give the new column a default value, and create a stub department named "Temp" to act as the default department. Par conséquent, les lignes Course existantes seront toutes associées au département « Temp » après l’exécution de la méthode Up.As a result, existing Course rows will all be related to the "Temp" department after the Up method runs.

  • Ouvrez le fichier {timestamp}_ComplexDataModel.cs.Open the {timestamp}_ComplexDataModel.cs file.

  • Commentez la ligne de code qui ajoute la colonne DepartmentID à la table Course.Comment out the line of code that adds the DepartmentID column to the Course table.

    migrationBuilder.AlterColumn<string>(
        name: "Title",
        table: "Course",
        maxLength: 50,
        nullable: true,
        oldClrType: typeof(string),
        oldNullable: true);
                
    //migrationBuilder.AddColumn<int>(
    //    name: "DepartmentID",
    //    table: "Course",
    //    nullable: false,
    //    defaultValue: 0);
    
  • Ajoutez le code en surbrillance suivant après le code qui crée la table Department :Add the following highlighted code after the code that creates the Department table:

    migrationBuilder.CreateTable(
        name: "Department",
        columns: table => new
        {
            DepartmentID = table.Column<int>(nullable: false)
                .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
            Budget = table.Column<decimal>(type: "money", nullable: false),
            InstructorID = table.Column<int>(nullable: true),
            Name = table.Column<string>(maxLength: 50, nullable: true),
            StartDate = table.Column<DateTime>(nullable: false)
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_Department", x => x.DepartmentID);
            table.ForeignKey(
                name: "FK_Department_Instructor_InstructorID",
                column: x => x.InstructorID,
                principalTable: "Instructor",
                principalColumn: "ID",
                onDelete: ReferentialAction.Restrict);
        });
    
    migrationBuilder.Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())");
    // Default value for FK points to department created above, with
    // defaultValue changed to 1 in following AddColumn statement.
    
    migrationBuilder.AddColumn<int>(
        name: "DepartmentID",
        table: "Course",
        nullable: false,
        defaultValue: 1);
    

Dans une application de production, vous devez écrire un code ou des scripts pour ajouter des lignes Department et associer des lignes Course aux nouvelles lignes Department.In a production application, you would write code or scripts to add Department rows and relate Course rows to the new Department rows. Vous n’avez alors plus besoin du département « Temp » ni de la valeur par défaut sur la colonne Course.DepartmentID.You would then no longer need the "Temp" department or the default value on the Course.DepartmentID column.

Enregistrez vos modifications et générez le projet.Save your changes and build the project.

Changer la chaîne de connexionChange the connection string

Vous avez maintenant un nouveau code dans la classe DbInitializer qui ajoute des données initiales pour les nouvelles entités à une base de données vide.You now have new code in the DbInitializer class that adds seed data for the new entities to an empty database. Pour faire en sorte qu’EF crée une nouvelle base de données vide, remplacez le nom de la base de données dans la chaîne de connexion ,dans appsettings.json, par ContosoUniversity3 ou un autre nom que vous n’avez pas utilisé sur l’ordinateur que vous utilisez.To make EF create a new empty database, change the name of the database in the connection string in appsettings.json to ContosoUniversity3 or some other name that you haven't used on the computer you're using.

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity3;Trusted_Connection=True;MultipleActiveResultSets=true"
  },

Enregistrez les modifications dans appsettings.json.Save your change to appsettings.json.

Notes

Comme alternative au changement de nom de la base de données, vous pouvez supprimer la base de données.As an alternative to changing the database name, you can delete the database. Utilisez l’Explorateur d’objets SQL Server (SSOX) ou la commande CLI database drop :Use SQL Server Object Explorer (SSOX) or the database drop CLI command:

dotnet ef database drop

Mettre à jour la base de donnéesUpdate the database

Une fois que vous avez modifié le nom de la base de données ou supprimé la base de données, exécutez la commande database update dans la fenêtre de commande pour exécuter les migrations.After you have changed the database name or deleted the database, run the database update command in the command window to execute the migrations.

dotnet ef database update

Exécutez l’application pour que la méthode DbInitializer.Initialize exécute la nouvelle base de données et la remplisse.Run the app to cause the DbInitializer.Initialize method to run and populate the new database.

Ouvrez la base de données dans SSOX comme vous l’avez fait précédemment, puis développez le nœud Tables pour voir que toutes les tables ont été créées.Open the database in SSOX as you did earlier, and expand the Tables node to see that all of the tables have been created. (Si SSOX est resté ouvert, cliquez sur le bouton Actualiser.)(If you still have SSOX open from the earlier time, click the Refresh button.)

Tables dans SSOX

Exécutez l’application pour déclencher le code d’initialiseur qui remplit la base de données.Run the app to trigger the initializer code that seeds the database.

Cliquez avec le bouton droit sur la table CourseAssignment et sélectionnez Afficher les données pour vérifier qu’elle comporte des données.Right-click the CourseAssignment table and select View Data to verify that it has data in it.

Données CourseAssignment dans SSOX

Obtenir le codeGet the code

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

Étapes suivantesNext steps

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

  • Personnaliser le modèle de donnéesCustomized the Data model
  • Apporter des modifications à l’entité StudentMade changes to Student entity
  • Créer une entité InstructorCreated Instructor entity
  • Créer une entité OfficeAssignmentCreated OfficeAssignment entity
  • Modifier l’entité CourseModified Course entity
  • Créer l’entité DepartmentCreated Department entity
  • Modifier l’entité EnrollmentModified Enrollment entity
  • Mettre à jour le contexte de base de donnéesUpdated the database context
  • Remplir la base de données avec des données de testSeeded database with test data
  • Ajouter une migrationAdded a migration
  • Changer la chaîne de connexionChanged the connection string
  • Mettre à jour la base de donnéesUpdated the database

Passez au tutoriel suivant pour en savoir plus sur l’accès aux données associées.Advance to the next tutorial to learn more about how to access related data.