Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Dans les didacticiels précédents, vous avez travaillé avec un modèle de données simple composé de trois entités. Dans ce tutoriel, vous allez ajouter d’autres entités et relations et personnaliser 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.
Lorsque vous aurez terminé, les classes d’entité composeront le modèle de données complet indiqué dans l’illustration suivante :
Dans ce tutoriel, vous allez :
- Personnaliser le modèle de données
- Apporter des modifications à l’entité Student
- Créer une entité Instructor
- Créer une entité OfficeAssignment
- Modifier l’entité Course
- Créer l’entité Department
- Modifier l’entité Enrollment
- Mettre à jour le contexte de la base de données
- Initialiser la base de données avec des données de test
- Ajouter une migration
- Modifier la chaîne de connexion
- Mettre à jour la base de données
Prerequisites
Personnaliser le modèle de données
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. 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 types d’entités restants dans le modèle.
L’attribut DataType
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. 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. Pour voir un exemple de la procédure à suivre, vous allez ajouter un attribut à la propriété EnrollmentDate dans la classe Student.
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 illustré dans l’exemple suivant :
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. Dans le cas présent, nous voulons uniquement effectuer le suivi de la date, pas de la date et de l’heure. L’énumération DataType fournit de nombreux types de données, tels que Date, Time, PhoneNumber, Currency, EmailAddress, etc. L’attribut DataType peut également permettre à l’application de fournir automatiquement des fonctionnalités propres au type. 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. L’attribut DataType émet des attributs HTML 5 data- compréhensibles par les navigateurs HTML 5. Les DataType attributs ne fournissent aucune validation.
DataType.Date ne spécifie pas le format de la date qui s’affiche. Par défaut, le champ de données s’affiche selon les formats par défaut basés sur CultureInfo du serveur.
L’attribut DisplayFormat est utilisé pour spécifier explicitement le format de date :
[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. (Vous ne souhaiterez peut-être pas que pour certains champs , par exemple, pour les valeurs monétaires, vous ne souhaiterez peut-être pas le symbole monétaire dans la zone de texte pour modification.)
Vous pouvez utiliser l’attribut DisplayFormat lui-même, mais il est généralement judicieux d’utiliser l’attribut DataType également. L’attribut DataType transmet la sémantique des données par opposition à la façon de l’afficher sur un écran et fournit les avantages suivants que vous n’obtenez DisplayFormatpas :
Le navigateur peut activer les fonctionnalités HTML5 (par exemple pour afficher un contrôle de calendrier, le symbole monétaire approprié aux paramètres régionaux, les liens de messagerie, certaines validations d’entrée côté client, etc.).
Par défaut, le navigateur affiche les données à l’aide du format correspondant à vos paramètres régionaux.
Pour plus d’informations, consultez la documentation relative au tag helper <input>.
Exécutez l’application, accédez à la page Index des étudiants et notez que les heures ne sont plus affichées pour les dates d’inscription. Il en va de même pour toute vue qui utilise le modèle Student.
L’attribut StringLength
Vous pouvez également spécifier des règles de validation de données et des messages d’erreur de validation à l’aide d’attributs. L’attribut StringLength définit la longueur maximale dans la base de données et fournit la validation côté client et côté serveur pour 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.
Supposons que vous voulez garantir que les utilisateurs n’entrent pas plus de 50 caractères pour un nom. Pour ajouter cette limitation, ajoutez des attributs StringLength aux propriétés FirstMidName et LastName, comme illustré dans l’exemple suivant :
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. Vous pouvez utiliser l’attribut RegularExpression pour appliquer des restrictions à l’entrée. Par exemple, le code suivant exige que le premier caractère soit en majuscule et que les autres caractères soient alphabétiques :
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
L’attribut MaxLength fournit des fonctionnalités similaires à l’attribut StringLength , mais ne fournit pas de validation côté client.
Le modèle de base de données a maintenant changé d’une manière qui nécessite une modification dans le schéma de base de données. Vous allez utiliser des migrations pour mettre à jour le schéma sans perdre de données que vous avez peut-être ajoutées à la base de données à l’aide de l’interface utilisateur de l’application.
Enregistrez vos modifications et générez le projet. Ouvrez ensuite la fenêtre de commandes dans le dossier du projet et entrez les commandes suivantes :
dotnet ef migrations add MaxLengthOnNames
dotnet ef database update
La migrations add commande avertit que la perte de données peut se produire, car la modification rend la longueur maximale plus courte pour deux colonnes. Migrations crée un fichier nommé <timeStamp>_MaxLengthOnNames.cs. Ce fichier contient du code dans la méthode Up qui mettra à jour la base de données pour qu’elle corresponde au modèle de données actuel. La commande database update a exécuté ce code.
L’horodatage préfixé au nom du fichier de migrations est utilisé par Entity Framework pour ordonner les migrations. Vous pouvez créer plusieurs migrations avant d’exécuter la commande update-database, puis toutes les migrations sont appliquées dans l’ordre dans lequel elles ont été créées.
Exécutez l’application, sélectionnez l’onglet Étudiants, cliquez sur Créer nouveau, puis essayez de saisir l’un ou l’autre des noms avec plus de 50 caractères. L’application doit vous empêcher d’effectuer cette opération.
Attribut Column
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. 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. 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. Pour effectuer ce mappage, vous pouvez utiliser l’attribut Column.
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. 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. Si vous ne spécifiez pas de noms de colonnes, ils ont le même nom que le nom de la propriété.
Dans le Student.cs fichier, ajoutez une using instruction pour System.ComponentModel.DataAnnotations.Schema et ajoutez l’attribut de nom de colonne à la FirstMidName propriété, comme indiqué dans le code mis en surbrillance suivant :
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 modifie le modèle sous-jacent à SchoolContext, de sorte qu’il ne correspond pas à la base de données.
Enregistrez vos modifications et générez le projet. Ouvrez ensuite la fenêtre de commandes dans le dossier du projet et entrez les commandes suivantes pour créer une autre migration :
dotnet ef migrations add ColumnFirstName
dotnet ef database update
Dans SQL Server Explorateur d'objets, ouvrez le concepteur de tables Student en double-cliquant sur la table Student.
Avant d’appliquer les deux premières migrations, les colonnes de nom étaient de type nvarchar(MAX). Ils sont désormais nvarchar(50) et le nom de colonne a changé de FirstMidName à FirstName.
Note
Si vous essayez de compiler avant de terminer la création de toutes les classes d’entité dans les sections suivantes, vous risquez d’obtenir des erreurs du compilateur.
Modifications apportées à l’entité Student
Dans Models/Student.cs, remplacez le code que vous avez ajouté précédemment par le code suivant. Les modifications sont mises en surbrillance.
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 Required
L’attribut Required fait des propriétés name des champs obligatoires. L’attribut Required n’est pas nécessaire pour les types non nullables tels que les types valeur (DateTime, int, double, float, etc.). Les types qui n’acceptent pas les valeurs null sont traités automatiquement comme des champs obligatoires.
L'attribut Required doit être utilisé avec MinimumLength pour que MinimumLength soit appliqué.
[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }
Attribut Display
L’attribut Display spécifie que la légende des zones de texte doit être « Prénom », « Nom », « Nom complet » et « Date d’inscription » au lieu du nom de propriété dans chaque instance (qui n’a pas d’espace pour diviser les mots).
Propriété calculée FullName
FullName est une propriété calculée qui retourne une valeur créée par concaténation de deux autres propriétés. Par conséquent, il n’a qu’un accesseur get, et aucune colonne FullName ne sera générée dans la base de données.
Créer une entité Instructor
Créez Models/Instructor.cs, en remplaçant le code de modèle par le code suivant :
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. Dans le didacticiel Implémentation de l’héritage plus loin dans cette série, vous remanierez ce code pour éliminer la redondance.
Vous pouvez placer plusieurs attributs sur une ligne. Vous pouvez donc également écrire les HireDate attributs comme suit :
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Propriétés de navigation CourseAssignments et OfficeAssignment
Les propriétés CourseAssignments et OfficeAssignment sont des propriétés de navigation.
Un formateur pouvant animer un nombre quelconque de cours, CourseAssignments est défini comme une 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. Vous pouvez spécifier ICollection<T> ou un type tel que List<T> ou HashSet<T>. Si vous spécifiez ICollection<T>, EF crée une collection HashSet<T> par défaut.
La raison pour laquelle il s’agit d’entités CourseAssignment est expliquée ci-dessous dans la section sur les relations plusieurs-à-plusieurs.
Les règles d’entreprise de Contoso University indiquent qu’un instructeur ne peut avoir qu’un seul bureau, de sorte que la OfficeAssignment propriété contient une seule entité OfficeAssignment (qui peut être null si aucun bureau n’est affecté).
public OfficeAssignment OfficeAssignment { get; set; }
Créer une entité OfficeAssignment
Créez Models/OfficeAssignment.cs avec le code suivant :
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 Key
Il existe une relation un-à-zéro-ou-un entre les entités Instructor et OfficeAssignment. Une assignation de bureau n'existe que par rapport à l'instructeur auquel elle est attribuée ; par conséquent, sa clé primaire est également sa clé étrangère pour l’entité Instructor. Mais Entity Framework ne peut pas reconnaître InstructorID automatiquement comme clé primaire de cette entité, car son nom ne suit pas la convention de nommage ID ou classnameID. Par conséquent, l’attribut Key est utilisé pour l’identifier comme clé :
[Key]
public int InstructorID { get; set; }
Vous pouvez également utiliser l’attribut Key si l’entité possède sa propre clé primaire, mais que vous souhaitez donner à la propriété un nom autre que classnameID ou ID.
Par défaut, EF traite la clé comme non générée par la base de données, car la colonne est destinée à une relation identifiante.
Propriété de navigation Instructor
L’entité Instructor a une propriété de navigation nullable OfficeAssignment (car un instructeur n’a peut-être pas d’affectation de bureau) et l’entité OfficeAssignment a une propriété de navigation non nullable Instructor (car une affectation de bureau ne peut pas exister sans un instructeur- InstructorID n’est pas nullable). Lorsqu’une entité Instructor a une entité OfficeAssignment associée, chaque entité aura une référence à l’autre dans sa propriété de navigation.
Vous pouvez placer un attribut [Required] sur la propriété de navigation Instructor pour spécifier qu’il doit y avoir un instructeur associé, mais vous n’avez pas à le faire, car la clé étrangère InstructorID (qui est également la clé de cette table) est non nullable.
Modifier l’entité Course
Dans Models/Course.cs, remplacez le code que vous avez ajouté précédemment par le code suivant. Les modifications sont mises en surbrillance.
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 possède une propriété DepartmentID de clé étrangère qui pointe vers l’entité Département associée et possède une propriété de navigation Department.
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. 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. 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. Par exemple, lorsque vous récupérez une Course entité à modifier, l’entité Department est null si vous ne la chargez pas. Par conséquent, lorsque vous mettez à jour l’entité Course , vous devrez d’abord extraire l’entité Department . Quand 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 d’effectuer la mise à jour.
L’attribut DatabaseGenerated
L’attribut DatabaseGenerated avec le None paramètre sur la CourseID propriété spécifie que les valeurs de clé primaire sont fournies par l’utilisateur plutôt que générées par la base de données.
[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. C’est ce que vous souhaitez dans la plupart des scénarios. Toutefois, pour les entités Course, vous allez utiliser un numéro de cours spécifié par l’utilisateur, par exemple la série 1 000 pour un service, la série 2 000 pour un autre service, etc.
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. Pour plus d’informations, consultez Propriétés générées.
Propriétés de clé étrangère et de navigation
Les propriétés de clé étrangère et les propriétés de navigation dans l’entité Course reflètent les relations suivantes :
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.
public int DepartmentID { get; set; }
public Department Department { get; set; }
Un cours pouvant avoir un nombre quelconque d’étudiants inscrits, la propriété de navigation Enrollments est une collection :
public ICollection<Enrollment> Enrollments { get; set; }
Un cours peut être enseigné par plusieurs instructeurs, de sorte que la CourseAssignments propriété de navigation est une collection (le type CourseAssignment est expliqué plus loin) :
public ICollection<CourseAssignment> CourseAssignments { get; set; }
Créer l’entité Department
Créez Models/Department.cs avec le code suivant :
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 Column
Précédemment, vous avez utilisé l’attribut Column pour modifier le mappage des noms de colonnes. Dans le code de l’entitéDepartment, l’attribut Column est utilisé pour modifier le mappage de type de données SQL afin que la colonne soit définie à l’aide du type SQL Server money dans la base de données :
[Column(TypeName="money")]
public decimal Budget { get; set; }
Le mappage de colonnes n’est généralement pas obligatoire, 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é. Le type CLR decimal est mappé à un type SQL Server decimal. Mais dans ce cas, vous savez que la colonne contiendra des montants monétaires et que le type de données monétaires est plus approprié pour cela.
Propriétés de clé étrangère et de navigation
Les propriétés de clé étrangère et de navigation reflètent les relations suivantes :
Un département peut ou non avoir un administrateur, et un administrateur est toujours un formateur. Par conséquent, la propriété InstructorID est incluse comme clé étrangère de l’entité Instructor, et un point d’interrogation est ajouté après la désignation de type int pour marquer la propriété comme nullable. La propriété de navigation est nommée Administrator , mais contient une entité Instructor :
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
Un département pouvant avoir de nombreux cours, il existe une propriété de navigation Courses :
public ICollection<Course> Courses { get; set; }
Note
Par convention, Entity Framework permet la suppression en cascade pour les clés étrangères non nullables et pour les relations plusieurs à plusieurs. Cela peut entraîner des règles de suppression en cascade circulaires, ce qui entraînera une exception lorsque vous essayerez d’ajouter une migration. Par exemple, si vous n’avez pas défini la propriété Department.InstructorID comme nullable, EF configurerait une règle de suppression en cascade pour supprimer le service lorsque vous supprimez l’instructeur, ce qui n’est pas ce que vous voulez voir se produire. Si vos règles d’entreprise exigeaient que la propriété InstructorID ne soit pas nullable, vous devriez utiliser l’instruction d’API Fluent suivante pour désactiver la suppression en cascade sur la relation :
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
Modifier l’entité Enrollment
Dans Models/Enrollment.cs, remplacez le code que vous avez ajouté précédemment par le code suivant :
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 navigation
Les propriétés de clé étrangère et de navigation reflètent les relations suivantes :
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 :
public int CourseID { get; set; }
public Course Course { get; set; }
Un enregistrement d’inscription concerne un seul étudiant, il y a donc une propriété de clé étrangère StudentID et une propriété de navigation Student :
public int StudentID { get; set; }
public Student Student { get; set; }
Relations plusieurs-à-plusieurs
Il existe une relation plusieurs-à-plusieurs entre les entités Student et Course. L’entité Enrollment fonctionne comme une table de jointure plusieurs-à-plusieurs avec une charge utile dans la base de données. « 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 le cas présent, une clé primaire et une propriété Grade).
L’illustration suivante montre à quoi ressemblent ces relations dans un diagramme d’entité. (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 du didacticiel, il est simplement utilisé ici comme illustration.)
Chaque ligne de relation comporte un 1 à une extrémité et un astérisque (*) à l’autre, ce qui indique une relation un-à-plusieurs.
Si la table Enrollment n’inclut pas d’informations relatives aux notes, elle doit uniquement contenir les deux clés étrangères CourseID et StudentID. Dans ce cas, il s’agirait d’une table de jointure plusieurs-à-plusieurs sans données supplémentaires (ou table de jointure pure) dans la base de données. Les entités Course et Instructor ont ce type de relation plusieurs-à-plusieurs, et votre étape suivante consiste à créer une classe d’entité pour servir de table de jointure sans charge utile.
EF Core prend en charge les tables de jointure implicites pour les relations plusieurs-à-plusieurs, mais ce didacticiel n’a pas été mis à jour pour utiliser une table de jointure implicite. Consultez relations plusieurs-à-plusieurs, la Razor version Pages de ce didacticiel qui a été mise à jour.
L’entité CourseAssignment
Créez Models/CourseAssignment.cs avec le code suivant :
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 jointure
Une table de jointure est requise dans la base de données pour la relation plusieurs-à-plusieurs Instructeurs-à-Cours, et elle doit être représentée par un ensemble d’entités. Il est courant de nommer une entité EntityName1EntityName2de jointure, qui dans ce cas serait CourseInstructor. Toutefois, nous vous recommandons de choisir un nom qui décrit la relation. Les modèles de données commencent simplement puis évoluent, avec des jointures sans payload qui se voient souvent ajouter un payload par la suite. Si vous commencez par un nom d’entité descriptif, vous n’aurez pas à modifier le nom ultérieurement. Dans l’idéal, l’entité de jointure aura son propre nom (éventuellement un mot unique) naturel dans le domaine d’entreprise. Par exemple, les livres et les clients peuvent être liés par le biais des évaluations. Pour cette relation, CourseAssignment c’est un meilleur choix que CourseInstructor.
Clé composite
Étant donné que les clés étrangères ne sont pas nullables et identifient de manière unique chaque ligne de la table, il n’est pas nécessaire d’utiliser une clé primaire distincte. Les propriétés CourseID et InstructorID doivent fonctionner comme une clé primaire composite. La seule façon d’identifier les clés primaires composites à EF consiste à utiliser l’API Fluent (cela ne peut pas être fait à l’aide d’attributs). Vous verrez comment configurer la clé primaire composite dans la section suivante.
La clé composite garantit que, bien que vous puissiez avoir plusieurs lignes pour un cours et plusieurs lignes pour un instructeur, vous ne pouvez pas avoir plusieurs lignes pour le même instructeur et le même cours. L’entité Enrollment de jointure définit sa propre clé primaire. Les doublons de ce type sont donc possibles. Pour éviter ces doublons, vous pouvez ajouter un index unique sur les champs de clé étrangère ou configurer Enrollment avec une clé composite primaire similaire à CourseAssignment. Pour plus d’informations, consultez Index.
Mettre à jour le contexte de la base de données
Ajoutez le code en surbrillance suivant au fichier Data/SchoolContext.cs :
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.
À propos d’une alternative fluide à l’API Fluent
Le code de la OnModelCreating méthode de la DbContext classe utilise l’API Fluent pour configurer le comportement EF. L’API est appelée « fluent », car elle est souvent utilisée en enchaînant une série d’appels de méthode en une seule instruction, comme dans cet exemple de la EF Core documentation :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
Dans ce tutoriel, vous utilisez l’API Fluent uniquement pour le mappage de base de données que vous ne pouvez pas faire avec les attributs. 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. Certains attributs, tels que MinimumLength, ne peuvent pas être appliqués avec l’API Fluent. Comme mentionné précédemment, MinimumLength ne modifie pas le schéma, il applique uniquement une règle de validation côté client et côté serveur.
Certains développeurs préfèrent utiliser exclusivement l’API Fluent pour pouvoir garder leurs classes d’entité « propres ». Vous pouvez mélanger des attributs et une API Fluent si vous le souhaitez. Il existe quelques personnalisations qui ne peuvent être effectuées qu’à l’aide de l’API Fluent. Toutefois, en règle générale, il est recommandé de choisir l’une de ces deux approches, et de l’utiliser de manière cohérente dans la mesure du possible. Si vous utilisez les deux, notez qu’en cas de conflit, l’API Fluent remplace les attributs.
Pour plus d’informations sur les attributs par rapport à l’API Fluent, consultez Méthodes de configuration.
Diagramme des entités montrant les relations
L’illustration suivante montre le diagramme que les outils Entity Framework Power Tools créent pour le modèle School complet.
En plus des 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 ainsi que la ligne de relation zéro-ou-un-à-plusieurs (0..1 à *) entre les entités Instructor et Department.
Initialiser la base de données avec des données de test
Remplacez le code dans le Data/DbInitializer.cs fichier par le code suivant pour fournir des données initiales pour les nouvelles entités que vous avez créées.
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 tutoriel, la majeure partie de ce code crée simplement de nouveaux objets d’entité et charge des exemples de données dans des propriétés selon les besoins des tests. Notez comment 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.
Ajouter une migration
Enregistrez vos modifications et générez le projet. Ouvrez ensuite la fenêtre de commande dans le dossier du projet et entrez la migrations add commande (ne faites pas encore la commande update-database) :
dotnet ef migrations add ComplexDataModel
Vous recevez un avertissement sur la perte de données possible.
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 essayiez d’exécuter la commande database update à ce stade (ne le faites pas encore), vous obtiendriez l’erreur suivante :
L’instruction ALTER TABLE est en conflit avec la contrainte FOREIGN KEY « FK_dbo.Course_dbo.Department_DepartmentID ». Le conflit s’est produit dans la base de données « ContosoUniversity », table « dbo.Department », colonne « 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. Le code généré dans la méthode Up ajoute une clé étrangère non nullable DepartmentID à la table Course. 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. Pour ce tutoriel, vous allez exécuter la migration sur une nouvelle base de données, mais dans une application de production, vous devrez faire en sorte que la migration gère les données existantes, de sorte que les instructions suivantes montrent un exemple de procédure à suivre.
Pour que cette migration fonctionne avec les données existantes, vous devez modifier le code pour donner à la nouvelle colonne une valeur par défaut et créer un département factice nommé « Temp » pour agir comme département par défaut. Par conséquent, les lignes de cours existantes sont toutes liées au service « Temp » après l’exécution de la Up méthode.
Ouvrez le fichier
{timestamp}_ComplexDataModel.cs.Mettez en commentaire la ligne de code qui ajoute la colonne DepartmentID à la table Course.
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 :
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 allez écrire du code ou des scripts pour ajouter des lignes Department et associer des lignes Course aux nouvelles lignes Department. Vous n’avez alors plus besoin du service « Temp » ni de la valeur par défaut de la colonne Course.DepartmentID.
Enregistrez vos modifications et générez le projet.
Modifier la chaîne de connexion
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. Pour qu’EF crée une base de données vide, remplacez le nom de la base de données dans la chaîne de connexion de appsettings.json par ContosoUniversity3 ou par un autre nom que vous n’avez pas utilisé sur l’ordinateur dont vous vous servez.
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity3;Trusted_Connection=True;MultipleActiveResultSets=true"
},
Enregistrez le changement apporté dans appsettings.json.
Note
En guise d’alternative à la modification du nom de la base de données, vous pouvez supprimer la base de données. Utilisez SQL Server Explorateur d'objets (SSOX) ou la database drop commande CLI :
dotnet ef database drop
Mettre à jour la base de données
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.
dotnet ef database update
Exécutez l’application pour que la méthode DbInitializer.Initialize s’exécute et remplisse la nouvelle base de données.
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. (Si vous avez toujours SSOX ouvert depuis tout à l’heure, cliquez sur le bouton Actualiser.)
Exécutez l’application pour déclencher le code d’initialiseur qui remplit la base de données.
Cliquez avec le bouton droit sur la table CourseAssignment et sélectionnez Afficher les données pour vérifier qu’elle contient des données.
Obtenir le code
Télécharger ou afficher l’application complète.
Étapes suivantes
Dans ce tutoriel, vous allez :
- Personnaliser le modèle de données
- Modifications apportées à l’entité Student
- Créer une entité Instructor
- Entité OfficeAssignment créée
- Modifier l’entité Course
- Entité Département créée
- Entité d’inscription modifiée
- Mise à jour du contexte de la base de données
- Base de données initialisée avec des données de test
- Ajout d’une migration
- Changer la chaîne de connexion
- Base de données mise à jour
Passez au tutoriel suivant pour en savoir plus sur l’accès aux données associées.