Partager via


Annotations de données Code First

Remarque

EF4.1 À partir de uniquement : les fonctionnalités, les API, etc. présentées dans cette page ont été introduites dans Entity Framework 4.1. Si vous utilisez une version antérieure, certaines ou toutes ces informations ne s’appliquent pas.

Le contenu de cette page est adapté d’un article écrit à l’origine par Julie Lerman (<http://thedatafarm.com>).

Entity Framework Code First vous permet d’utiliser vos propres classes de domaine pour représenter le modèle sur lequel EF s’appuie pour effectuer des requêtes, un suivi des modifications et des fonctions de mise à jour. Code First tire parti d’un modèle de programmation appelé « convention over configuration » Code First part du principe que vos classes suivent les conventions d’Entity Framework et, dans ce cas, vont automatiquement déterminer comment effectuer son travail. Toutefois, si vos classes ne suivent pas ces conventions, vous avez la possibilité d’ajouter des configurations à vos classes pour fournir à EF les informations requises.

Code First vous donne deux façons d’ajouter ces configurations à vos classes. L’un utilise des attributs simples appelés DataAnnotations, et le second utilise l’API Fluent de Code First, qui vous offre un moyen de décrire les configurations impérativement, dans le code.

Cet article se concentre sur l’utilisation de DataAnnotations (dans l’espace de noms System.ComponentModel.DataAnnotations) pour configurer vos classes mettant en évidence les configurations les plus couramment nécessaires. Les DataAnnotations sont également comprises par un certain nombre d’applications .NET, telles que ASP.NET MVC, ce qui permet à ces applications de tirer parti des mêmes annotations pour les validations côté client.

Modèle

Je vais montrer Code First DataAnnotations avec une paire simple de classes : Blog et Billet.

    public class Blog
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string BloggerName { get; set;}
        public virtual ICollection<Post> Posts { get; set; }
    }

    public class Post
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public DateTime DateCreated { get; set; }
        public string Content { get; set; }
        public int BlogId { get; set; }
        public ICollection<Comment> Comments { get; set; }
    }

Les classes Blog et Post suivent commodément la convention « code first » et ne nécessitent aucun ajustement pour permettre la compatibilité avec EF. Toutefois, vous pouvez également utiliser les annotations pour fournir plus d’informations à EF sur les classes et la base de données à laquelle elles sont mappées.

 

Clé :

Entity Framework s’appuie sur chaque entité ayant une valeur de clé utilisée pour le suivi des entités. Une convention de Code First est des propriétés de clé implicites ; Le code First recherche une propriété nommée « ID » ou une combinaison de nom de classe et d’« ID », comme « BlogId ». Cette propriété est mappée à une colonne clé primaire dans la base de données.

Les classes Blog et Post suivent toutes les deux cette convention. Que se passe-t-il s’ils ne l’ont pas fait ? Que se passe-t-il si Blog a utilisé le nom PrimaryTrackingKey à la place, ou même foo? Si le premier code ne trouve pas de propriété correspondant à cette convention, il lèvera une exception en raison de l'exigence d'Entity Framework selon laquelle vous devez avoir une propriété clé. Vous pouvez utiliser l’annotation de clé pour spécifier la propriété à utiliser comme EntityKey.

    public class Blog
    {
        [Key]
        public int PrimaryTrackingKey { get; set; }
        public string Title { get; set; }
        public string BloggerName { get; set;}
        public virtual ICollection<Post> Posts { get; set; }
    }

Si vous utilisez la fonctionnalité de génération de base de données du premier code, la table blog dispose d’une colonne clé primaire nommée PrimaryTrackingKey, également définie comme Identity par défaut.

Table Blog avec clé primaire

Clés composites

Entity Framework prend en charge les clés composites : clés primaires qui se composent de plusieurs propriétés. Par exemple, vous pouvez avoir une classe Passport dont la clé primaire est une combinaison de PassportNumber et d’IssuingCountry.

    public class Passport
    {
        [Key]
        public int PassportNumber { get; set; }
        [Key]
        public string IssuingCountry { get; set; }
        public DateTime Issued { get; set; }
        public DateTime Expires { get; set; }
    }

La tentative d’utilisation de la classe ci-dessus dans votre modèle EF entraînerait une InvalidOperationException:

Impossible de déterminer l’ordre de clé primaire composite pour le type « Passport ». Utilisez ColumnAttribute ou la méthode HasKey pour spécifier un ordre pour les clés primaires composites.

Pour utiliser des clés composites, Entity Framework vous oblige à définir un ordre pour les propriétés de clé. Pour ce faire, utilisez l’annotation Colonne pour spécifier un ordre.

Remarque

La valeur d’ordre est relative (plutôt que basée sur l’index) afin que toutes les valeurs puissent être utilisées. Par exemple, 100 et 200 seraient acceptables à la place de 1 et 2.

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

Si vous avez des entités avec des clés étrangères composites, vous devez spécifier le même ordre de colonne que celui utilisé pour les propriétés de clé primaire correspondantes.

Seul l’ordre relatif dans les propriétés de clé étrangère doit être identique, les valeurs exactes affectées à Order n’ont pas besoin de correspondre. Par exemple, dans la classe suivante, 3 et 4 peuvent être utilisés à la place de 1 et 2.

    public class PassportStamp
    {
        [Key]
        public int StampId { get; set; }
        public DateTime Stamped { get; set; }
        public string StampingCountry { get; set; }

        [ForeignKey("Passport")]
        [Column(Order = 1)]
        public int PassportNumber { get; set; }

        [ForeignKey("Passport")]
        [Column(Order = 2)]
        public string IssuingCountry { get; set; }

        public Passport Passport { get; set; }
    }

Requis

L’annotation Required indique à EF qu’une propriété particulière est requise.

L’ajout obligatoire à la propriété Title force EF (et MVC) à s’assurer que la propriété contient des données.

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

Sans modification supplémentaire du code ou du balisage dans l’application, une application MVC effectue une validation côté client, même en créant dynamiquement un message à l’aide des noms de propriété et d’annotation.

Page de création avec l’erreur « Le titre est requis »

L’attribut Obligatoire affecte également la base de données générée en rendant la propriété mappée non nullable. Notez que le champ Titre a changé en valeur « non Null ».

Remarque

Dans certains cas, il est possible que la colonne de la base de données ne soit pas nullable même si la propriété est requise. Par exemple, lorsque vous utilisez des données de stratégie d’héritage TPH pour plusieurs types, elles sont stockées dans une table unique. Si un type dérivé inclut une propriété obligatoire, la colonne ne peut pas être rendue non Nullable, car tous les types de la hiérarchie auront cette propriété.

 

Table Blogs

 

MaxLength et MinLength

Les attributs MaxLength et MinLength vous permettent de spécifier des validations de propriétés supplémentaires, comme vous l’avez fait avec Required.

Voici le BloggerName avec des exigences de longueur. L’exemple montre également comment combiner des attributs.

    [MaxLength(10),MinLength(5)]
    public string BloggerName { get; set; }

L’annotation MaxLength affecte la base de données en définissant la longueur de la propriété sur 10.

Table Blogs affichant la longueur maximale sur la colonne BloggerName

L’annotation côté client MVC et l’annotation côté serveur EF 4.1 honorent toutes les deux cette validation, de nouveau en créant dynamiquement un message d’erreur : « le champ BloggerName doit être un type de chaîne ou de tableau dont la longueur maximale est « 10 ».» Ce message est un peu long. De nombreuses annotations vous permettent de spécifier un message d’erreur avec l’attribut ErrorMessage.

    [MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
    public string BloggerName { get; set; }

Vous pouvez également spécifier ErrorMessage dans l’annotation requise.

Page de création avec un message d’erreur personnalisé

 

NotMapped

La première convention de code détermine que chaque propriété d’un type de données pris en charge est représentée dans la base de données. Mais ce n’est pas toujours le cas dans vos applications. Par exemple, vous pouvez avoir une propriété dans la classe Blog qui crée un code basé sur les champs Title et BloggerName. Cette propriété peut être créée dynamiquement et n’a pas besoin d’être stockée. Vous pouvez marquer toutes les propriétés qui ne sont pas mappées à la base de données avec l’annotation NotMapped telle que cette propriété BlogCode.

    [NotMapped]
    public string BlogCode
    {
        get
        {
            return Title.Substring(0, 1) + ":" + BloggerName.Substring(0, 1);
        }
    }

 

ComplexType

Il n’est pas rare de décrire vos entités de domaine dans un ensemble de classes, puis de calquer ces classes pour décrire une entité complète. Par exemple, vous pouvez ajouter une classe appelée BlogDetails à votre modèle.

    public class BlogDetails
    {
        public DateTime? DateCreated { get; set; }

        [MaxLength(250)]
        public string Description { get; set; }
    }

Notez que BlogDetails n’a aucun type de propriété de clé. Dans la conception pilotée par le domaine, BlogDetails est appelée objet valeur. Entity Framework fait référence à des objets valeur comme des types complexes.  Les types complexes ne peuvent pas être suivis eux-mêmes.

Toutefois, en tant que propriété dans la classe Blog , BlogDetails sera suivi dans le cadre d’un objet Blog. Pour que le code reconnaisse d’abord cela, vous devez marquer la classe BlogDetails en tant que ComplexType.

    [ComplexType]
    public class BlogDetails
    {
        public DateTime? DateCreated { get; set; }

        [MaxLength(250)]
        public string Description { get; set; }
    }

Vous pouvez maintenant ajouter une propriété dans la classe Blog pour représenter le BlogDetails pour ce blog.

        public BlogDetails BlogDetail { get; set; }

Dans la base de données, la table Blog contient toutes les propriétés du blog, y compris les propriétés contenues dans sa propriété BlogDetail. Par défaut, chacun d’eux est précédé du nom du type complexe « BlogDetail ».

Table Blog avec un type complexe

ConcurrencyCheck

L’annotation ConcurrencyCheck vous permet d’indiquer une ou plusieurs propriétés à utiliser pour la vérification de la concurrence dans la base de données lorsqu’un utilisateur modifie ou supprime une entité. Si vous avez travaillé avec ef Designer, cela s’aligne sur la définition de la ConcurrencyMode d’une propriété sur Fixed.

Voyons comment fonctionne ConcurrencyCheck en l’ajoutant à la propriété BloggerName.

    [ConcurrencyCheck, MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
    public string BloggerName { get; set; }

Lorsque SaveChanges est appelée, en raison de l’annotation ConcurrencyCheck sur le champ BloggerName , la valeur d’origine de cette propriété sera utilisée dans la mise à jour. La commande tente de localiser la ligne correcte en filtrant non seulement sur la valeur de clé, mais également sur la valeur d’origine de BloggerName.  Voici les parties critiques de la commande UPDATE envoyées à la base de données, où vous pouvez voir que la commande met à jour la ligne qui a un PrimaryTrackingKey est 1 et une BloggerName de « Julie » qui était la valeur d’origine lorsque ce blog a été récupéré à partir de la base de données.

    where (([PrimaryTrackingKey] = @4) and ([BloggerName] = @5))
    @4=1,@5=N'Julie'

Si quelqu’un a changé le nom du blogueur pour ce blog en attendant, cette mise à jour échoue et vous obtiendrez une DbUpdateConcurrencyException que vous devrez gérer.

 

TimeStamp

Il est plus courant d’utiliser des champs rowversion ou timestamp pour la vérification de la concurrence. Mais 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. Le code traite d’abord Timestamp propriétés identiques aux propriétés ConcurrencyCheck , mais il garantit également que le champ de base de données généré par le code est non nullable. Vous ne pouvez avoir qu’une propriété timestamp dans une classe donnée.

Ajout de la propriété suivante à la classe Blog :

    [Timestamp]
    public Byte[] TimeStamp { get; set; }

entraîne la création d’une colonne d’horodatage non nullable dans la table de base de données.

Table Blogs avec une colonne de timestamp

 

Table et colonne

Si vous laissez Code First créer la base de données, vous pouvez modifier le nom des tables et des colonnes qu’il crée. Vous pouvez également utiliser Code First avec une base de données existante. Mais ce n’est pas toujours le cas que les noms des classes et propriétés de votre domaine correspondent aux noms des tables et colonnes de votre base de données.

Ma classe est nommée Blog et par convention, le code présume d’abord que cela mappe à une table nommée Blogs. Si ce n’est pas le cas, vous pouvez spécifier le nom de la table avec l’attribut Table. Par exemple, l’annotation spécifie que le nom de la table est InternalBlogs.

    [Table("InternalBlogs")]
    public class Blog

L’annotation Column est plus adepte dans la spécification des attributs d’une colonne mappée. Vous pouvez stipuler un nom, un type de données ou même l’ordre dans lequel une colonne apparaît dans la table. Voici un exemple de l’attribut Column.

    [Column("BlogDescription", TypeName="ntext")]
    public String Description {get;set;}

Ne confondez pas l’attribut TypeName column avec DataType DataAnnotation. DataType est une annotation utilisée pour l’interface utilisateur et est ignorée par Code First.

Voici la table après avoir été régénérée. Le nom de la table a changé en InternalBlogs et Description colonne du type complexe est désormais BlogDescription. Étant donné que le nom a été spécifié dans l’annotation, le code n’utilise pas la convention de démarrage du nom de colonne avec le nom du type complexe.

Table Blogs et colonne renommées

 

DatabaseGenerated

Une fonctionnalité importante de base de données est la possibilité d’avoir des propriétés calculées. Si vous mappez vos classes Code First aux tables qui contiennent des colonnes calculées, vous ne souhaitez pas que Entity Framework essaie de mettre à jour ces colonnes. Toutefois, vous souhaitez qu’EF retourne ces valeurs à partir de la base de données après avoir inséré ou mis à jour des données. Vous pouvez utiliser l’annotation DatabaseGenerated pour marquer ces propriétés dans votre classe, ainsi que l’énumération Computed. D’autres énumérations sont None et Identity.

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime DateCreated { get; set; }

Vous pouvez utiliser la base de données générée sur des colonnes d’octets ou d’horodatage lorsque le code génère d’abord la base de données. Sinon, vous ne devez l’utiliser que lorsque vous pointez vers des bases de données existantes, car le code ne pourra pas déterminer la formule de la colonne calculée.

Vous avez lu ci-dessus par défaut, une propriété de clé qui est un entier deviendra une clé d’identité dans la base de données. Cela serait identique à la définition DatabaseGenerated à DatabaseGeneratedOption.Identity. Si vous ne souhaitez pas qu’il s’agit d’une clé d’identité, vous pouvez définir la valeur sur DatabaseGeneratedOption.None.

 

Index

Remarque

EF6.1 et versions ultérieures uniquement : 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 entraîne la création de l’index correspondant dans la base de données lorsqu’elle crée la base de données, ou génère la structure des appels CreateIndex correspondants si vous utilisez Code First Migrations.

Par exemple, le code suivant entraîne la création d’un index sur la colonne Rating de la table Posts dans la base de données.

    public class Post
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        [Index]
        public int Rating { get; set; }
        public int BlogId { get; set; }
    }

Par défaut, l’index est nommé IX_<nom de propriété> (IX_Rating dans l’exemple ci-dessus). Vous pouvez également spécifier un nom pour l’index. L’exemple suivant spécifie que l’index doit être nommé PostRatingIndex.

    [Index("PostRatingIndex")]
    public int Rating { get; set; }

Par défaut, les index ne sont pas uniques, mais vous pouvez utiliser le paramètre nommé IsUnique pour spécifier qu’un index doit être unique. L’exemple suivant présente un index unique sur le nom de connexion d’un User.

    public class User
    {
        public int UserId { get; set; }

        [Index(IsUnique = true)]
        [StringLength(200)]
        public string Username { get; set; }

        public string DisplayName { get; set; }
    }

Index à plusieurs colonnes

Les index qui s’étendent sur plusieurs colonnes sont spécifiés à l’aide du même nom dans plusieurs annotations d’index pour une table donnée. Lorsque vous créez des index à plusieurs colonnes, vous devez spécifier un ordre pour les colonnes de l’index. Par exemple, le code suivant crée un index à plusieurs colonnes sur Rating et BlogId appelé IX_BlogIdAndRating. BlogId est la première colonne de l’index et Rating est la seconde.

    public class Post
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        [Index("IX_BlogIdAndRating", 2)]
        public int Rating { get; set; }
        [Index("IX_BlogIdAndRating", 1)]
        public int BlogId { get; set; }
    }

 

Attributs de relation : InverseProperty et ForeignKey

Remarque

Cette page fournit des informations sur la configuration des relations dans votre modèle Code First à l’aide d’annotations de données. Pour obtenir des informations générales sur les relations dans EF et sur l’accès et la manipulation des données à l’aide de relations, consultez Relations et propriétés de navigation.*

La première convention de code prend en charge les relations les plus courantes dans votre modèle, mais il existe certains cas où il a besoin d’aide.

La modification du nom de la propriété de clé dans la classe Blog a créé un problème avec sa relation avec Post

Lors de la génération de la base de données, le code voit d’abord la propriété BlogId dans la classe Post et le reconnaît, par la convention qu’elle correspond à un nom de classe plus Id, en tant que clé étrangère à la classe Blog. Mais il n’y a pas de propriété BlogId dans la classe de blog. La solution pour cela consiste à créer une propriété de navigation dans Post et à utiliser la ForeignKey DataAnnotation pour aider le code à comprendre d’abord comment créer la relation entre les deux classes (à l’aide de la propriété Post.BlogId ) ainsi que la façon de spécifier des contraintes dans la base de données.

    public class Post
    {
            public int Id { get; set; }
            public string Title { get; set; }
            public DateTime DateCreated { get; set; }
            public string Content { get; set; }
            public int BlogId { get; set; }
            [ForeignKey("BlogId")]
            public Blog Blog { get; set; }
            public ICollection<Comment> Comments { get; set; }
    }

La contrainte dans la base de données affiche une relation entre InternalBlogs.PrimaryTrackingKey et Posts.BlogId

Relation entre InternalBlogs.PrimaryTrackingKey et Posts.BlogId

La InverseProperty est utilisée lorsque vous avez plusieurs relations entre les classes.

Dans la classe Post, vous pouvez suivre qui a écrit un billet de blog et qui l’a modifié. Voici deux nouvelles propriétés de navigation pour la classe Post.

    public Person CreatedBy { get; set; }
    public Person UpdatedBy { get; set; }

Vous devez également ajouter la classe Person référencée par ces propriétés. La classe Person a des propriétés de navigation à la Post, une pour tous les billets écrits par la personne et une pour toutes les publications mises à jour par cette personne.

    public class Person
    {
            public int Id { get; set; }
            public string Name { get; set; }
            public List<Post> PostsWritten { get; set; }
            public List<Post> PostsUpdated { get; set; }
    }

Le code n’est d’abord pas en mesure de faire correspondre les propriétés dans les deux classes. La table de base de données pour Posts doit avoir une clé étrangère pour la personne CreatedBy et une pour la personne UpdatedBy, mais le code crée d’abord quatre propriétés de clé étrangère : Person_Id, Person_Id1, CreatedBy_Id et UpdatedBy_Id.

Table Posts avec des clés étrangères supplémentaires

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

    [InverseProperty("CreatedBy")]
    public List<Post> PostsWritten { get; set; }

    [InverseProperty("UpdatedBy")]
    public List<Post> PostsUpdated { get; set; }

Étant donné que la propriété PostsWritten dans Person sait que cela fait référence au type Post, elle génère la relation avec Post.CreatedBy. De même, PostsUpdated sera connecté à Post.UpdatedBy. Le code ne crée pas d’abord les clés étrangères supplémentaires.

Table Posts sans clés étrangères supplémentaires

 

Résumé

Les DataAnnotations vous permettent non seulement de décrire la validation côté client et côté serveur dans vos premières classes de code, mais elles vous permettent également d’améliorer et même de corriger les hypothèses que le code fera d’abord sur vos classes en fonction de ses conventions. Avec DataAnnotations, vous pouvez non seulement générer une génération de schéma de base de données, mais également mapper vos premières classes de code à une base de données préexistante.

Même s’ils sont très flexibles, gardez à l’esprit que les DataAnnotations fournissent uniquement les modifications de configuration les plus couramment nécessaires que vous pouvez apporter sur vos premières classes de code. Pour configurer vos classes pour certains des cas de périphérie, vous devez rechercher l’autre mécanisme de configuration, l’API Fluent de Code First.