Entity Framework - Annotations de données

DataAnnotations est utilisé pour configurer les classes qui mettront en évidence les configurations les plus couramment nécessaires. Les annotations de données sont également comprises par un certain nombre d'applications .NET, telles que ASP.NET MVC qui permet à ces applications d'exploiter les mêmes annotations pour les validations côté client. Les attributs DataAnnotation remplacent les conventions CodeFirst par défaut.

System.ComponentModel.DataAnnotations inclut les attributs suivants qui ont un impact sur la nullité ou la taille de la colonne.

  • Key
  • Timestamp
  • ConcurrencyCheck
  • Required
  • MinLength
  • MaxLength
  • StringLength

System.ComponentModel.DataAnnotations.Schema namespace inclut les attributs suivants qui ont un impact sur le schéma de la base de données.

  • Table
  • Column
  • Index
  • ForeignKey
  • NotMapped
  • InverseProperty

Clé

Entity Framework repose sur chaque entité ayant une valeur de clé qu'elle utilise pour le suivi des entités. L'une des conventions dont dépend Code First est la façon dont elle implique quelle propriété est la clé dans chacune des classes Code First.

  • La convention consiste à rechercher une propriété nommée «Id» ou une propriété qui combine le nom de la classe et «Id», comme «StudentId».

  • La propriété correspondra à une colonne de clé primaire dans la base de données.

  • Les classes d'étudiant, de cours et d'inscription suivent cette convention.

Supposons maintenant que la classe Student utilise le nom StdntID au lieu de ID. Lorsque Code First ne trouve pas de propriété qui correspond à cette convention, il lèvera une exception en raison de l'exigence d'Entity Framework selon laquelle vous devez avoir une propriété de clé. Vous pouvez utiliser l'annotation de clé pour spécifier la propriété à utiliser comme EntityKey.

Jetons un coup d'œil au code suivant d'une classe Student qui contient StdntID, mais il ne suit pas la convention Code First par défaut. Donc, pour gérer cela, un attribut Key est ajouté qui en fera une clé primaire.

public class Student {

   [Key]
   public int StdntID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Lorsque vous exécutez votre application et examinez votre base de données dans SQL Server Explorer, vous verrez que la clé primaire est désormais StdntID dans la table des étudiants.

Entity Framework prend également en charge les clés composites. Composite keyssont également des clés primaires constituées de plusieurs propriétés. Par exemple, vous avez une classe DrivingLicense dont la clé primaire est une combinaison de LicenseNumber et IssuingCountry.

public class DrivingLicense {

   [Key, Column(Order = 1)]
   public int LicenseNumber { get; set; }
   [Key, Column(Order = 2)]
   public string IssuingCountry { get; set; }
   public DateTime Issued { get; set; }
   public DateTime Expires { get; set; }
}

Lorsque vous avez des clés composites, Entity Framework vous oblige à définir un ordre des propriétés de clé. Vous pouvez le faire en utilisant l'annotation Colonne pour spécifier une commande.

Horodatage

Code First traitera les propriétés d'horodatage de la même manière que les propriétés ConcurrencyCheck, mais il garantira également que le champ de base de données que le code génère en premier n'est pas Nullable.

  • Il est plus courant d'utiliser des champs rowversion ou timestamp pour la vérification de la concurrence.

  • Plutôt que d'utiliser l'annotation ConcurrencyCheck, vous pouvez utiliser l'annotation TimeStamp plus spécifique tant que le type de la propriété est un tableau d'octets.

  • Vous ne pouvez avoir qu'une seule propriété d'horodatage dans une classe donnée.

Jetons un coup d'œil à un exemple simple en ajoutant la propriété TimeStamp à la classe Course -

public class Course {

   public int CourseID { get; set; }
   public string Title { get; set; }
   public int Credits { get; set; }
   [Timestamp]
   public byte[] TStamp { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Comme vous pouvez le voir dans l'exemple ci-dessus, l'attribut Timestamp est appliqué à la propriété Byte [] de la classe Course. Ainsi, Code First créera une colonne d'horodatage TStampdans la table Courses.

Vérification de la concurrence

L'annotation ConcurrencyCheck vous permet de marquer une ou plusieurs propriétés à utiliser pour la vérification d'accès concurrentiel dans la base de données lorsqu'un utilisateur modifie ou supprime une entité. Si vous avez travaillé avec EF Designer, cela correspond à la définition de ConcurrencyMode d'une propriété sur Fixed.

Jetons un coup d'œil à un exemple simple du fonctionnement de ConcurrencyCheck en l'ajoutant à la propriété Title dans la classe Course.

public class Course {

   public int CourseID { get; set; }
   [ConcurrencyCheck]
   public string Title { get; set; }
   public int Credits { get; set; }
   [Timestamp, DataType("timestamp")]
   public byte[] TimeStamp { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Dans la classe Course ci-dessus, l'attribut ConcurrencyCheck est appliqué à la propriété Title existante. Désormais, Code First inclura la colonne Titre dans la commande de mise à jour pour vérifier la concurrence optimiste comme indiqué dans le code suivant.

exec sp_executesql N'UPDATE [dbo].[Courses]
   SET [Title] = @0
   WHERE (([CourseID] = @1) AND ([Title] = @2))
   ',N'@0 nvarchar(max) ,@1 int,@2 nvarchar(max) ',@0=N'Maths',@1=1,@2=N'Calculus'
go

Annotation requise

L'annotation requise indique à EF qu'une propriété particulière est requise. Jetons un coup d'œil à la classe Student suivante dans laquelle l'ID requis est ajouté à la propriété FirstMidName. L'attribut obligatoire forcera EF à s'assurer que la propriété contient des données.

public class Student {

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

   [Required]
   public string LastName { get; set; }

   [Required]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Comme vu dans l'exemple ci-dessus, l'attribut requis est appliqué à FirstMidName et LastName. Ainsi, Code First créera des colonnes NOT NULL FirstMidName et LastName dans la table Students, comme illustré dans l'image suivante.

Longueur maximale

L'attribut MaxLength vous permet de spécifier des validations de propriété supplémentaires. Il peut être appliqué à une propriété de type chaîne ou tableau d'une classe de domaine. EF Code First définira la taille d'une colonne comme spécifié dans l'attribut MaxLength.

Jetons un coup d'œil à la classe Course suivante dans laquelle l'attribut MaxLength (24) est appliqué à la propriété Title.

public class Course {

   public int CourseID { get; set; }
   [ConcurrencyCheck]
   [MaxLength(24)]
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Lorsque vous exécutez l'application ci-dessus, Code First crée un titre de colonne nvarchar (24) dans la table CourseId, comme illustré dans l'image suivante.

Lorsque l'utilisateur définit le titre qui contient plus de 24 caractères, EF lèvera EntityValidationError.

Longueur minimale

L'attribut MinLength vous permet également de spécifier des validations de propriétés supplémentaires, comme vous l'avez fait avec MaxLength. L'attribut MinLength peut également être utilisé avec l'attribut MaxLength comme indiqué dans le code suivant.

public class Course {

   public int CourseID { get; set; }
   [ConcurrencyCheck]
   [MaxLength(24) , MinLength(5)]
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

EF lèvera EntityValidationError, si vous définissez une valeur de propriété Title inférieure à la longueur spécifiée dans l'attribut MinLength ou supérieure à la longueur spécifiée dans l'attribut MaxLength.

Longueur de chaine

StringLength vous permet également de spécifier des validations de propriétés supplémentaires telles que MaxLength. La seule différence est que l'attribut StringLength ne peut être appliqué qu'à une propriété de type chaîne de classes Domain.

public class Course {

   public int CourseID { get; set; }
   [StringLength (24)]
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Entity Framework valide également la valeur d'une propriété pour l'attribut StringLength. Si l'utilisateur définit le Title qui contient plus de 24 caractères, EF lancera EntityValidationError.

Table

La convention Code par défaut First crée un nom de table similaire au nom de classe. Si vous laissez Code First créer la base de données et souhaitez également modifier le nom des tables qu'il crée. Puis -

  • Vous pouvez utiliser Code First avec une base de données existante. Mais ce n'est pas toujours le cas que les noms des classes correspondent aux noms des tables de votre base de données.

  • L'attribut de table remplace cette convention par défaut.

  • EF Code First créera une table avec un nom spécifié dans l'attribut Table pour une classe de domaine donnée.

Jetons un coup d'œil à l'exemple suivant dans lequel la classe est nommée Student et par convention, Code First suppose que cela correspondra à une table nommée Students. Si ce n'est pas le cas, vous pouvez spécifier le nom de la table avec l'attribut Table comme indiqué dans le code suivant.

[Table("StudentsInfo")]
public class Student {

   [Key]
   public int StdntID { get; set; }
   [Required]
   public string LastName { get; set; }
   [Required]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Vous pouvez maintenant voir que l'attribut Table spécifie la table en tant que StudentsInfo. Lorsque la table est générée, vous verrez le nom de table StudentsInfo comme indiqué dans l'image suivante.

Vous pouvez non seulement spécifier le nom de la table, mais vous pouvez également spécifier un schéma pour la table à l'aide de l'attribut Table, comme indiqué dans le code suivant.

[Table("StudentsInfo", Schema = "Admin")] 
public class Student {

   [Key]
   public int StdntID { get; set; }
   [Required]
   public string LastName { get; set; }
   [Required]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Vous pouvez voir dans l'exemple ci-dessus, la table est spécifiée avec le schéma d'administration. Maintenant, Code First va créer la table StudentsInfo dans le schéma Admin, comme indiqué dans l'image suivante.

Colonne

Il est également identique à l'attribut Table, mais l'attribut Table remplace le comportement de la table tandis que l'attribut Column remplace le comportement de la colonne. La convention Code par défaut First crée un nom de colonne similaire au nom de propriété. Si vous laissez Code First créer la base de données et souhaitez également modifier le nom des colonnes de vos tables. Puis -

  • L'attribut de colonne remplace la convention par défaut.

  • EF Code First créera une colonne avec un nom spécifié dans l'attribut Column pour une propriété donnée.

Jetons un coup d'œil à l'exemple suivant dans lequel la propriété est nommée FirstMidName et par convention, Code First suppose que cela correspondra à une colonne nommée FirstMidName.

Si ce n'est pas le cas, vous pouvez spécifier le nom de la colonne avec l'attribut Column comme indiqué dans le code suivant.

public class Student {

   public int ID { get; set; }
   public string LastName { get; set; }
   [Column("FirstName")]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Vous pouvez voir que l'attribut Column spécifie la colonne comme FirstName. Lorsque la table est générée, vous verrez le nom de la colonne FirstName comme indiqué dans l'image suivante.

Indice

L'attribut Index a été introduit dans Entity Framework 6.1. Si vous utilisez une version antérieure, les informations de cette section ne s'appliquent pas.

  • Vous pouvez créer un index sur une ou plusieurs colonnes à l'aide de IndexAttribute.

  • L'ajout de l'attribut à une ou plusieurs propriétés obligera EF à créer l'index correspondant dans la base de données lors de la création de la base de données.

  • Les index rendent la récupération des données plus rapide et plus efficace, dans la plupart des cas. Cependant, la surcharge d'une table ou d'une vue avec des index peut affecter de manière désagréable les performances d'autres opérations telles que les insertions ou les mises à jour.

  • L'indexation est la nouvelle fonctionnalité d'Entity Framework qui vous permet d'améliorer les performances de votre application Code First en réduisant le temps requis pour interroger les données de la base de données.

  • Vous pouvez ajouter des index à votre base de données à l'aide de l'attribut Index et remplacer les paramètres Unique et Clustered par défaut pour obtenir l'index le mieux adapté à votre scénario.

  • Par défaut, l'index sera nommé IX_ <nom de la propriété>

Jetons un coup d'œil au code suivant dans lequel l'attribut Index est ajouté dans la classe Course pour les crédits.

public class Course {
   public int CourseID { get; set; }
   public string Title { get; set; }
   [Index]
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Vous pouvez voir que l'attribut Index est appliqué à la propriété Credits. Lorsque la table est générée, vous verrez IX_Credits dans les index.

Par défaut, les index ne sont pas uniques, mais vous pouvez utiliser le IsUniqueparamètre nommé pour spécifier qu'un index doit être unique. L'exemple suivant présente un index unique comme indiqué dans le code suivant.

public class Course {
   public int CourseID { get; set; }
   [Index(IsUnique = true)]
	
   public string Title { get; set; }
   [Index]
	
   public int Credits { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Clé étrangère

La convention Code First s'occupe des relations les plus courantes dans votre modèle, mais dans certains cas, elle a besoin d'aide. Par exemple, en modifiant le nom de la propriété de clé dans la classe Student, un problème avec sa relation avec la classe d'inscription a été créé.

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
   public Grade? Grade { get; set; }
	
   public virtual Course Course { get; set; }
   public virtual Student Student { get; set; }
}

public class Student {
   [Key]
   public int StdntID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Lors de la génération de la base de données, Code First voit la propriété StudentID dans la classe Enrollment et la reconnaît, par la convention qu'elle correspond à un nom de classe plus «ID», comme une clé étrangère de la classe Student. Toutefois, il n'existe aucune propriété StudentID dans la classe Student, mais la propriété StdntID est la classe Student.

La solution pour cela consiste à créer une propriété de navigation dans l'inscription et à utiliser le ForeignKey DataAnnotation pour aider Code First à comprendre comment créer la relation entre les deux classes comme indiqué dans le code suivant.

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
	
   public Grade? Grade { get; set; }
   public virtual Course Course { get; set; }
   [ForeignKey("StudentID")]
	
   public virtual Student Student { get; set; }
}

Vous pouvez maintenant voir que l'attribut ForeignKey est appliqué à la propriété de navigation.

Non mappé

Par défaut, les conventions de Code First, chaque propriété qui est d'un type de données pris en charge et qui inclut des getters et des setters est représentée dans la base de données. Mais ce n'est pas toujours le cas dans vos applications. L'attribut NotMapped remplace cette convention par défaut. Par exemple, vous pouvez avoir une propriété dans la classe Student telle que FatherName, mais elle n'a pas besoin d'être stockée. Vous pouvez appliquer l'attribut NotMapped à une propriété FatherName dont vous ne souhaitez pas créer de colonne dans la base de données, comme indiqué dans le code suivant.

public class Student {
   [Key]
   public int StdntID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
	
   public DateTime EnrollmentDate { get; set; }
   [NotMapped]

   public int FatherName { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Vous pouvez voir que l'attribut NotMapped est appliqué à la propriété FatherName. Lorsque la table est générée, vous verrez que la colonne FatherName ne sera pas créée dans une base de données, mais elle est présente dans la classe Student.

Code First ne crée pas de colonne pour une propriété, qui n'a ni getters ni setters, comme illustré dans l'exemple suivant des propriétés Address et Age de la classe Student.

Propriété inverse

InverseProperty est utilisé lorsque vous avez plusieurs relations entre les classes. Dans la classe d'inscription, vous souhaiterez peut-être garder une trace de qui s'est inscrit au cours actuel et au cours précédent. Ajoutons deux propriétés de navigation pour la classe Enrollment.

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
   public Grade? Grade { get; set; }
	
   public virtual Course CurrCourse { get; set; }
   public virtual Course PrevCourse { get; set; }
   public virtual Student Student { get; set; }
}

De même, vous devrez également ajouter la classe Course référencée par ces propriétés. La classe Course a des propriétés de navigation vers la classe Inscription, qui contient toutes les inscriptions actuelles et précédentes.

public class Course {

   public int CourseID { get; set; }
   public string Title { get; set; }
   [Index]

   public int Credits { get; set; }
   public virtual ICollection<Enrollment> CurrEnrollments { get; set; }
   public virtual ICollection<Enrollment> PrevEnrollments { get; set; }
}

Code First crée la colonne de clé étrangère {Nom de la classe} _ {Clé primaire}, si la propriété de clé étrangère n'est pas incluse dans une classe particulière, comme indiqué dans les classes ci-dessus. Lorsque la base de données est générée, vous verrez les clés étrangères suivantes.

Comme vous pouvez le voir, Code first n'est pas en mesure de faire correspondre les propriétés des deux classes par lui-même. La table de base de données pour les inscriptions doit avoir une clé étrangère pour le CurrCourse et une pour le PrevCourse, mais Code First créera quatre propriétés de clé étrangère, c'est-à-dire

  • CurrCourse _CourseID
  • PrevCourse _CourseID
  • Course_CourseID et
  • Course_CourseID1

Pour résoudre ces problèmes, vous pouvez utiliser l'annotation InverseProperty pour spécifier l'alignement des propriétés.

public class Course {

   public int CourseID { get; set; }
   public string Title { get; set; }
   [Index]

   public int Credits { get; set; }
   [InverseProperty("CurrCourse")]

   public virtual ICollection<Enrollment> CurrEnrollments { get; set; }
   [InverseProperty("PrevCourse")]

   public virtual ICollection<Enrollment> PrevEnrollments { get; set; }
}

Comme vous pouvez le voir, l'attribut InverseProperty est appliqué dans la classe Course ci-dessus en spécifiant à quelle propriété de référence de la classe Enrollment il appartient. Maintenant, Code First va générer une base de données et créer uniquement deux colonnes de clé étrangère dans la table Enrollments, comme illustré dans l'image suivante.

Nous vous recommandons d'exécuter l'exemple ci-dessus étape par étape pour une meilleure compréhension.