Cours EntityFramework CodeFirst
Cours EntityFramework CodeFirst
Cours EntityFramework CodeFirst
Architecture
Entities
•Convention
•Data
Annotation
Service Layer •FluentAPI
Entities
Code First Conventions
Code First vous permet de décrire un modèle à l'aide de classes C# ou Visual Basic .NET. La forme de
base du modèle est détectée à l'aide de conventions. Les conventions sont des ensembles de règles
utilisées pour configurer automatiquement un modèle conceptuel selon des définitions de classe lors
de l'utilisation de Code First. Les conventions sont définies dans l'espace de noms
System.Data.Entity.ModelConfiguration.Conventions.
Une liste détaillée des conventions Code First est disponible dans la documentation de l'API. Cette
rubrique fournit une vue d'ensemble des conventions utilisées par Code First.
Découverte du type
Lorsque vous utilisez le développement Code First, vous commencez généralement par écrire des
classes .NET Framework qui définissent votre modèle conceptuel (domaine). En plus de définir les
classes, vous devez indiquer à DbContext quels types vous souhaitez inclure dans le modèle. Pour
cela, vous définissez une classe de contexte qui dérive de DbContext et expose les
propriétés DbSet pour les types qui doivent faire partie du modèle. Code First va inclure ces types et
extraire les types référencés, même si les types référencés sont définis dans un assembly distinct.
Si les types participent à une hiérarchie d'héritage, il suffit de définir une propriété DbSet de la classe
de base, et les types dérivés seront inclus automatiquement, s'ils se trouvent dans le même assembly
que la classe de base.
Dans l'exemple suivant, il n'existe qu'une seule propriété DbSet définie sur la
classe SchoolEntities (Departments). Code First utilise cette propriété pour découvrir et extraire les
types référencés.
// Navigation property
public virtual ICollection<Course> Courses { get; set; }
}
// Foreign key
public int DepartmentID { get; set; }
// Navigation properties
public virtual Department Department { get; set; }
}
modelBuilder.Ignore<Department>();
...
Convention de relation
Dans Entity Framework, les propriétés de navigation fournissent une façon permettant d'accéder à
une relation entre types d'entité. Chaque objet peut avoir une propriété de navigation pour chaque
relation à laquelle il participe. Les propriétés de navigation vous permettent d'accéder aux relations
et de les gérer dans les deux sens, en retournant soit un objet référence (si la multiplicité est un ou
zéro-ou-un) ou une collection (si la multiplicité est plusieurs). Code First déduit les relations en
fonction des propriétés de navigation définies sur les types.
Outre les propriétés de navigation, il est recommandé d'inclure des propriétés de clé étrangère sur
les types qui représentent des objets dépendants. Toute propriété du même type que la propriété
principale de clé primaire et avec un nom qui respecte l'un des formats suivants représente une clé
étrangère de la relation : « <nom de la propriété de navigation><nom de propriété de clé primaire
principal> », « <nom de classe principal><nom de propriété de clé primaire> » ou « <nom de
propriété de clé primaire principale> ». Si plusieurs correspondances sont détectées, la priorité est
donnée l'ordre indiqué ci-dessus. La détection de clé étrangère ne respecte pas la casse. Lorsqu'une
propriété de clé étrangère est détectée, Code First déduit la multiplicité de la relation en fonction de
la possibilité de valeur NULL de la clé étrangère. Si la propriété accepte la valeur Null, la relation est
enregistrée comme étant facultative ; sinon, la relation est enregistrée comme étant obligatoire.
Si une clé étrangère sur l'entité dépendante n'accepte pas la valeur NULL, Code First définit la
suppression en cascade dans la relation. Si une clé étrangère sur l'entité dépendante accepte la
valeur Null, Code First ne définit pas la suppression en cascade dans la relation, et lorsque le principal
est supprimé, la clé étrangère prend la valeur Null. La multiplicité et le comportement de suppression
en cascade détectés par convention peuvent être remplacés en utilisant l'API Fluent.
Dans l'exemple suivant, les propriétés de navigation et une clé étrangère sont utilisées pour définir la
relation entre les classes Department et Course.
// Navigation property
public virtual ICollection<Course> Courses { get; set; }
}
// Foreign key
public int DepartmentID { get; set; }
// Navigation properties
public virtual Department Department { get; set; }
}
Remarque :Si vous avez plusieurs relations entre les mêmes types (par exemple, supposons que vous
définissez les classes Person et Book, où la classe Person contient les propriétés de
navigation ReviewedBooks et AuthoredBooks et la classe Book contient les propriétés de
navigation Author et Reviewer) vous devez configurer manuellement les relations à l'aide des
annotations de données ou de l'API Fluent.
namespace Demo.EF
public BloggingContext()
Dans cet exemple, DbContext utilise le nom qualifié de l'espace de noms de la classe de contexte
dérivé, Demo.EF.Bl oggingContext, en tant que nom de la base de données et crée une chaîne de
connexion pour cette base de données à l'aide de SQL Express ou LocalDb. Si les deux sont installés,
SQL Express est utilisé.
Visual Studio 2010 inclut SQL Express par défaut et Visual Studio 2012 inclut LocalDb. Pendant
l'installation, le package EntityFramework NuGet vérifie quel serveur de base de données est
disponible. Ce package met ensuite à jour le fichier de configuration en définissant le serveur de base
de données par défaut utilisé par Code First lors de la création d'une connexion par convention. Si
SQL server Express est en cours d'exécution, il est utilisé. Si SQL Express n'est pas disponible, LocalDb
sera enregistré comme valeur par défaut à la place. Aucune modification n'est apportée au fichier de
configuration s'il contient déjà un paramètre pour la structure de connexion par défaut.
b- Utiliser Code First avec une connexion par convention et un nom de base de données spécifié
Si vous n'avez effectué aucune autre configuration dans votre application, l'appel du constructeur de
chaîne sur DbContext avec le nom de la base de données à utiliser entraîne l'exécution de DbContext
en mode Code First avec une connexion de base de données créée par convention dans la base de
données portant ce nom. Par exemple :
public BloggingContext()
: base("BloggingDatabase")
Dans cet exemple DbContext utilise « BloggingDatabase » comme nom de base de données et crée
une chaîne de connexion pour cette base de données à l'aide de SQL Express (installé avec Visual
Studio 2010) ou de LocalDb (installé avec Visual Studio 2012). Si les deux sont installés, SQL Express
est utilisé.
C - Utiliser Code First avec une chaîne de connexion dans le fichier app.config/web.config
Vous pouvez choisir d'insérer une chaîne de connexion dans le fichier app.config ou web.config. Par
exemple :
<configuration>
<connectionStrings>
<add name="BloggingCompactDatabase"
providerName="System.Data.SqlServerCe.4.0"
connectionString="Data Source=Blogging.sdf"/>
</connectionStrings>
</configuration>
Cela constitue un moyen facile d'indiquer à DbContext d'utiliser un serveur de base de données autre
que SQL Express ou LocalDb. L'exemple ci-dessus spécifie une base de données SQL Server Compact
Edition.
Si le nom de la chaîne de connexion correspond au nom du contexte (avec ou sans qualification de
l'espace de noms), il est trouvé par DbContext lorsque le constructeur sans paramètre est utilisé. Si le
nom de la chaîne de connexion est différent du nom de contexte, indiquez à DbContext d'utiliser
cette connexion en mode Code First en passant le nom de la chaîne de connexion au constructeur
DbContext. Par exemple :
public BloggingContext()
: base("BloggingCompactDatabase")
Ou bien, vous pouvez utiliser la forme « name=<chaîne de connexion> » pour la chaîne passée au
constructeur DbContext. Par exemple :
public BloggingContext()
: base("name=BloggingCompactDatabase")
Cette forme rend explicite la chaîne de connexion attendue dans le fichier de configuration. Une
exception est levée si une chaîne de connexion avec le nom donné est introuvable.
Conventions de suppression
Les conventions définies dans l'espace de noms System.Data.Entity.ModelConfiguration.Conventions
peuvent être supprimées. L'exemple suivant supprime PluralizingTableNameConvention.
...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
Annotations de données Code First
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 s'appuie Entity Framework pour l'exécution des requêtes, le suivi des
modifications et la mise à jour des fonctions. Code First tire parti d'un modèle de programmation
appelé « convention sur la configuration ». Cela signifie que Code First suppose que vos classes
respectent les conventions utilisées par Entity Framework. Dans ce cas, Entity Framework est en
mesure d'élaborer les informations nécessaires pour réaliser ses tâches. Toutefois, si vos classes ne
respectent pas ces conventions, vous avez la possibilité d'ajouter des configurations aux classes de
façon à fournir les informations nécessaires à Entity Framework.
Code First propose deux façons d'ajouter ces configurations à vos classes. L'une consiste à utiliser des
attributs simples appelés DataAnnotations et l'autre à utiliser l'API Fluent Code First, ce qui vous
permet de décrire les configurations impérativement, dans le code.
Cet article est axé sur l'utilisation de DataAnnotations (dans l'espace de noms
System.ComponentModel.DataAnnotations) pour configurer les classes et met l'accent sur les
configurations fréquemment utilisées. Les attributs DataAnnotations sont également reconnus par
plusieurs application .NET, telles qu'ASP.NET MVC qui permet à ces applications d'exploiter les
mêmes annotations pour les validations côté client.
Je vais illustrer les attributs DataAnnotations Code First avec une simple paire de classes : Blog et
Post.
Les classes Blog et Post respectent la convention Code First et aucune modification n'est nécessaire
pour qu'elles soient compatibles avec Entity Framework. Vous pouvez également utiliser les
annotations pour fournir plus d'informations à Entity Framework 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 suivre les entités.
L'une des conventions dont dépend Code First repose sur la façon dont elle implique quelle propriété
est la clé dans chacune des classes Code First. Cette convention consiste à rechercher une propriété
nommée « Id » ou une qui associe le nom de classe et « Id », tel que « BlogId ». La propriété est
mappée à une colonne de clé primaire dans la base de données.
Les classes Blog et Post respectent cette convention. Que se passerait-il si ce n'était pas le cas ? Que
se passe-t-il si Blog utilise PrimaryTrackingKey à la place oufoo ? Si Code First ne trouve pas de
propriété qui correspond à cette convention, une exception est levée, car vous devez disposer d'une
propriété de clé dans Entity Framework. Utilisez l'annotation Key pour spécifier la propriété qui doit
être utilisée comme EntityKey.
Si vous utilisez la fonctionnalité de création de base de données Code First, la table Blog aura une
colonne clé primaire nommée PrimaryTrackingKey, qui est également définie comme identité par
défaut.
Obligatoire
L'annotation Required indique à Entity Framework qu'une propriété spécifique est requise.
Le fait d'ajouter Required à la propriété Title force Entity Framework (et MVC) à vérifier 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
effectuera une validation côté client, et générera même dynamiquement un message en utilisant les
noms de propriété et d'annotation.
L'attribut Required affecte également la base de données générée en transformant la propriété
mappée en propriété non-nullable. Notez que le champ Titre est devenu « not null ».
[MaxLength et MinLength
Les attributs MaxLength et MinLength vous permettent de spécifier des validations de propriété
supplémentaires, comme vous l'avez fait avec l'attribut Required.
[MaxLength(10),MinLength(5)]
public string BloggerName { get; set; }
L'annotation côté client MVC et l'annotation côté serveur Entity Framework 4.1 respectent cette
validation, en créant dynamiquement un message d'erreur : « Le champ BloggerName doit être une
chaîne ou un type de tableau ayant une longueur maximale de « 10 ». Ce message est un peu long.
De nombreuses annotations vous permettent de spécifier un message d'erreur avec l'attribut
ErrorMessage.
NotMapped
Une convention Code First stipule que chaque propriété qui est d'un type de données pris en charge
doit être représentée dans la base de données. Mais ce n'est pas toujours le cas dans vos
applications. Par exemple, vous pouvez disposer d'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 enregistrée. Vous pouvez marquer des propriétés qui ne correspondent pas à 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 sur un jeu de classes et de superposer ces classes
pour décrire une entité complète. Par exemple, ajoutez une classe appelée BlogDetails à votre
modèle.
[MaxLength(250)]
public string Description { get; set; }
}
Notez que BlogDetails n'a aucune propriété de type de clé. En mode de conception pilotée par le
domaine, BlogDetails s'appelle un objet de valeur. Entity Framework fait référence à des objets de
valeur en tant que types complexes. Les types complexes ne peuvent pas être suivis seuls.
Toutefois, en tant que propriété dans la classe Blog, BlogDetails sera suivi dans le cadre d'un objet
Blog. Pour que Code First le reconnaisse, vous devez marquer la classe BlogDetails comme
ComplexType.
[ComplexType]
public class BlogDetails
{
public DateTime? DateCreated { get; set; }
[MaxLength(250)]
public string Description { get; set; }
}
Vous pouvez à présent ajouter une propriété dans la classe Blog pour représenter la classe
BlogDetails de ce blog.
Dans la base de données, la table Blog contient toutes les propriétés de blog, y compris les propriétés
contenues dans la propriété BlogDetail. Par défaut, chaque propriété est précédée par le nom du
type complexe, BlogDetail.
Autre point intéressant, bien que la propriété DateCreated a été définie en tant que DateTime non
null dans la classe, le champ de la base de données concerné accepte la valeur Null. Vous devez
utiliser l'annotation Required si vous voulez affecter le schéma de la base de données.
ConcurrencyCheck
L'annotation ConcurrencyCheck vous permet de marquer une ou plusieurs propriétés à utiliser pour
le contrôle d'accès concurrentiel dans la base de données lorsqu'un utilisateur modifie ou supprime
une entité. Si vous utilisez Entity Framework Designer, cela correspond à l'affectation de la valeur
Fixed à l'attribut ConcurrencyMode d'une propriété.
Si quelqu'un a modifié le nom du blogueur pour ce blog dans le même temps, cette mise à jour
échoue et vous obtenez une exception DbUpdateConcurrencyException que vous devez gérer.
TimeStamp
Il est plus courant d'utiliser les champs rowversion ou timestamp pour le contrôle d'accès
concurrentiel. Plutôt que d'utiliser l'annotation ConcurrencyCheck, utilisez l'annotation TimeStamp
plus spécifique tant que le type de la propriété est un tableau d'octets. Code First traite les
propriétés Timestamp de la même façon que les propriétés ConcurrencyCheck, mais garantit
également que le champ de base de données généré par Code First n'est pas Null. Vous ne pouvez
avoir qu'une propriété Timestamp dans une classe donnée.
[Timestamp]
public Byte[] TimeStamp { get; set; }
entraîne la création d'une colonne Timestamp n'acceptant pas les valeurs null dans la table de base
de données.
Table et Column
Si vous laissez Code First créer la base de données, vous pouvez modifier le nom des tables et des
colonnes créées. Vous pouvez également utiliser Code First avec une base de données existante.
Cependant, les noms des classes et des propriétés dans votre domaine ne correspondent pas
toujours aux noms des tables et des colonnes de votre base de données.
La classe est nommée Blog et par convention, Code First suppose qu'elle sera mappée à une table
nommée Blogs. Si ce n'est pas le cas, spécifiez le nom de la table à l'aide de l'attribut Table. Ici par
exemple, l'annotation spécifie le nom de la table est InternalBlogs.
[Table("InternalBlogs")]
public class Blog
L'annotation Column est plus adaptée à 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;}
Voici la table après qu'elle a été régénérée. Le nom de la table a été remplacé par InternalBlogs et la
colonne Description du type complexe est maintenant BlogDescription. Étant donné que le nom a été
spécifié dans l'annotation, Code First n'utilise pas la convention consistant à faire commencer le nom
de colonne par le nom du type complexe.
DatabaseGenerated
Une des fonctionnalités essentielles d'une base de données est la possibilité de disposer de
propriétés calculées. Si vous mappez les classes Code First à des tables qui contiennent des colonnes
calculées, vous ne souhaitez pas qu'Entity Framework mette à jour ces colonnes. Cependant, vous
souhaitez qu'Entity Framework retourne ces valeurs de la base de données après que vous avez
inséré ou mis à jour les données. Utilisez l'annotation DatabaseGenerated pour marquer ces
propriétés dans la classe avec l'énumération Computed. None et Identity constituent d'autres enums.
[DatabaseGenerated(DatabaseGenerationOption.Computed)]
public DateTime DateCreated { get; set; }
Vous pouvez utiliser une base de données générée sur les colonnes byte ou timestamp lorsque Code
First génère la base de données ; sinon vous ne devez utiliser cette opération que lorsque vous
pointez sur des bases de données existantes, car Code First ne peut pas déterminer la formule de la
colonne calculée.
Vous avez lu ci-avant que par défaut, une propriété de clé qui est un entier devient une clé d'identité
dans la base de données. Cela revient à affecter DatabaseGenerated à
DatabaseGenerationOption.Identity. Si vous ne souhaitez pas qu'il sagisse d'une clé d'identité,
affectez la valeur à DatabaseGenerationOption.None.
La convention Code First gère les relations les plus courantes dans votre modèle, mais il existe des
cas où une assistance est nécessaire.
La modification du nom de la propriété de clé de la classe Blog a créé un problème avec sa relation
dans la classe Post.
Lors de la génération de la base de données, Code First détecte la propriété BlogId dans la classe Post
et la reconnaît, par convention qui correspond à un nom de classe et « ID », en tant que clé étrangère
dans la classe Blog. Mais il n'existe aucune propriété BlogId dans la classe Blog. La solution à ce
problème consiste à créer une propriété de navigation dans la classe Post et à utiliser Foreign
DataAnnotation pour permettre à Code First de comprendre comment établir la relation entre les
deux classes (à l'aide de la propriété Post.BlogId) et comment spécifier des contraintes dans la base
de données.
InverseProperty est utilisé lorsqu'il existe plusieurs relations entre les classes.
Dans la classe Post, vous pouvez assurer le suivi de l'utilisateur qui a écrit une publication de blog,
ainsi que de celui qui l'a modifiée. Voici deux nouvelles propriétés de navigation de 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. Cette classe possède
des propriétés de navigation dans la classe Post, une pour toutes les publications écrites par
l'utilisateur et une pour toutes les publications mises à jour par celui-ci.
Code First ne peut pas faire correspondre les propriétés dans les deux classes seul. La table de la base
de données des publications doit avoir une clé étrangère pour l'utilisateur CreatedBy et une pour
l'utilisateur UpdatedBy, mais Code First crée quatre propriétés de clé étrangère : Person_Id,
Person_Id1, CreatedBy_Id et UpdatedBy_Id.
Pour résoudre ces problèmes, vous pouvez utiliser l'annotation InverseProperty afin de spécifier la
correspondance 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 sait que cela fait référence au type Post, elle établit la
relation avec Post.CreatedBy. De même, PostsUpdated est connecté à Post.UpdatedBy. Et Code First
ne crée pas de clés étrangères supplémentaires.
Résumé
DataAnnotations vous permet non seulement de décrire la validation côté client et côté serveur dans
vos classes Code First, mais également d'améliorer et de corriger les hypothèses que Code First fera
sur vos classes en fonction de ses conventions. DataAnnotations vous permet non seulement de
piloter la génération du schéma de base de données, mais également de mapper vos classes Code
First à une base de données préexistante.
Même s'ils sont extrêmement flexibles, gardez à l'esprit que les attributs DataAnnotations
fournissent uniquement les modifications de configuration fréquemment utilisées que vous pouvez
effectuer dans vos classes Code First. Pour configurer les classes de certaines cases de bord, vous
devez examiner un autre mécanisme de configuration, l'API Fluent Code First.
Configuration/mappage des propriétés et des types avec l'API Fluent
Lorsque vous utilisez Entity Framework Code First, le comportement par défaut consiste à mapper
vos classes POCO aux tables à l'aide d'un jeu de conventions intégrées à Entity Framework. Dans
certains cas, toutefois, vous ne pouvez pas ou ne souhaitez pas respecter les conventions et vous
devez mapper les à autre chose que ce qui est dicté par les conventions.
Il existe deux méthodes principales pour configurer Entity Framework à cet effet lorsque vous
mappez des données, à savoir les annotations ou l'API Fluent Entity Framework. Les annotations
couvrent uniquement un sous-ensemble des fonctionnalités de l'API Fluent. Par conséquent, certains
scénarios ne sont pas possibles à l'aide des annotations. Cet article est conçu pour illustrer comment
utiliser l'API Fluent pour configurer les propriétés.
En plus de contrôler le mappage, l'API Fluent et les annotations peuvent également être utilisées
pour configurer les contraintes, telles que la longueur de champ ou obligatoires. Une fois
configurées, ces contraintes affectent la base de données créée par Code First, ainsi que la validation
effectuée par Entity Framework.
Introduction
Mappage de propriétés
La méthode Property est utilisée pour configurer les attributs de chaque propriété appartenant à une
entité ou à un type complexe. Elle permet d'obtenir un objet de configuration pour une propriété
donnée. Les options de l'objet de configuration sont spécifiques au type configuré ; IsUnicode est
disponible uniquement dans les propriétés de chaîne par exemple.
Pour définir explicitement une propriété de façon à ce qu'elle soit une clé primaire, utilisez la
méthode HasKey. Dans l'exemple suivant, la méthode HasKey est utilisée pour configurer la clé
primaire InstructorID sur le type OfficeAssignment.
L'exemple suivant configure les propriétés DepartmentID et Name comme clé primaire composite du
type Department.
Dans l'exemple suivant, la propriété Name ne doit pas dépasser 50 caractères. Si la propriété
contient plus de 50 caractères, vous obtenez une exceptionDbEntityValidationException. Si Code First
crée une base de données à partir de ce modèle, il définit également la longueur maximale de la
colonne Name à 50 caractères.
Dans l'exemple suivant, la propriété Name est obligatoire. Si vous ne spécifiez pas la propriété Name,
vous obtenez une exception DbEntityValidationException. Si Code First crée une base de données à
partir de ce modèle, la colonne utilisée pour stocker cette propriété est non nullable.
Spécification de NOT pour mapper une propriété CLR à une colonne dans la base de données
L'exemple suivant indique comment spécifier qu'une propriété sur un type CLR n'est pas mappée à
une colonne dans la base de données.
Mappage d'une propriété CLR à une colonne spécifique dans la base de données
L'exemple suivant mappe la propriété CLR Name à la colonne de base de données DepartmentName.
modelBuilder.Entity<Department>()
.Property(t => t.Name)
.HasColumnName("DepartmentName");
Affectation d'un nouveau nom à une clé étrangère qui n'est pas définie dans le modèle
Si vous choisissez de ne pas définir de clé étrangère sur un type CLR, mais souhaitez spécifier le nom
à utiliser dans la base de données, procédez comme suit :
modelBuilder.Entity<Course>()
.HasRequired(c => c.Department)
.WithMany(t => t.Courses)
.Map(m => m.MapKey("ChangedDepartmentID"));
Par défaut, les chaînes sont Unicode (nvarchar dans SQL Server). Utilisez la méthode IsUnicode pour
spécifier qu'une chaîne doit être de type varchar.
modelBuilder.Entity<Department>()
.Property(t => t.Name)
.IsUnicode(false);
modelBuilder.Entity<Department>()
.Property(p => p.Name)
.HasColumnType("varchar");
Il existe deux manières de configurer les propriétés scalaires sur un type complexe.
modelBuilder.ComplexType<Details>()
.Property(t => t.Location)
.HasMaxLength(20);
Ou utilisez la notation par points pour accéder à une propriété de type complexe.
modelBuilder.Entity<OnsiteCourse>()
.Property(t => t.Details.Location)
.HasMaxLength(20);
Pour spécifier qu'une propriété dans une entité représente un jeton de concurrence, utilisez l'attribut
ConcurrencyCheck ou la méthode IsConcurrencyToken.
modelBuilder.Entity<OfficeAssignment>()
.Property(t => t.Timestamp)
.IsConcurrencyToken();
Vous pouvez également utiliser la méthode IsRowVersion pour configurer la propriété comme
version de ligne dans la base de données. La configuration de la propriété en version de ligne la
configure automatiquement comme jeton de concurrence optimiste.
modelBuilder.Entity<OfficeAssignment>()
.Property(t => t.Timestamp)
.IsRowVersion();
Mappage de type
Par convention, un type qui n'a pas de clé primaire spécifiée est traité comme un type complexe. Il
existe des scénarios dans lesquels Code First ne détecte pas de type complexe (par exemple, si vous
avez une propriété nommée ID, mais vous ne souhaitez pas qu'il s'agisse d'une clé primaire). Dans ce
cas, vous devez utiliser l'API Fluent pour spécifier qu'un type est un type complexe.
modelBuilder.ComplexType<Details>();
Spécifier NOT pour mapper un type d'entité CLR à une table dans la base de données.
L'exemple suivant montre comment exclure un type CLR du mappage à une table dans la base de
données.
modelBuilder.Ignore<OnlineCourse>();
Mappage d'un type d'entité CLR à une table spécifique dans la base de données
Toutes les propriétés de Department sont mappées aux colonnes dans une table appelée
t_Department.
modelBuilder.Entity<Department>()
.ToTable("t_Department");
modelBuilder.Entity<Department>()
.ToTable("t_ Department", "school");
Dans ce scénario de mappage de table par hiérarchie, tous les types d'une hiérarchie d'héritage sont
mappés à une seule table. Une colonne de discriminateur est utilisée pour identifier le type de
chaque ligne. Lorsque vous créez votre modèle avec Code First, la stratégie table par hiérarchie
constitue la stratégie par défaut pour les types qui participent à la hiérarchie d'héritage. Par défaut,
la colonne de discriminateur est ajoutée à la table portant le nom « discriminateur » et le nom de
type CLR de chaque type dans la hiérarchie est utilisé pour les valeurs de discriminateur. Modifiez le
comportement par défaut à l'aide de l'API Fluent.
modelBuilder.Entity<Course>()
.Map<Course>(m => m.Requires("Type").HasValue("Course"))
.Map<OnsiteCourse>(m => m.Requires("Type").HasValue("OnsiteCourse"));
Dans le scénario de mappage table par type, tous les types sont mappés à des tables individuelles.
Les propriétés qui appartiennent uniquement à un type de base ou à un type dérivé sont stockées
dans une table qui mappe à ce type. Les tables qui mappent aux types dérivés stockent également
une clé étrangère qui associe la table dérivée avec la table de base.
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<OnsiteCourse>().ToTable("OnsiteCourse");
Dans le scénario de mappage table par type concret, chacun des types non abstraits dans la
hiérarchie est mappé à une table individuelle. Les tables qui mappent aux classes dérivées n'ont
aucune relation avec la table qui mappe à la classe de base dans la base de données. Toutes les
propriétés d'une classe, notamment les propriétés héritées, sont mappées aux colonnes de la table
correspondante.
Appelez la méthode MapInheritedProperties pour configurer chaque type dérivé.
MapInheritedProperties remappe toutes les propriétés qui ont été héritées de la classe de base à de
nouvelles colonnes dans la table pour la classe dérivée.
Remarque : étant donné que les tables qui participent à la hiérarchie d'héritage table par type
concret ne partagent pas de clé primaire, il y aura des clés d'entité en double lors de l'insertion dans
les tables qui sont mappées aux sous-classes si vous avez des valeurs générées par base de données
avec la même valeur initiale d'identité. Pour résoudre ce problème, spécifiez une valeur de départ
d'origine différente pour chaque table ou désactivez l'identité sur la propriété de clé primaire.
Identity est la valeur par défaut pour les propriétés de clé entières lorsque vous utilisez Code First.
modelBuilder.Entity<Course>()
.Property(c => c.CourseID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<OnsiteCourse>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("OnsiteCourse");
});
modelBuilder.Entity<OnlineCourse>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("OnlineCourse");
});
Mappage des propriétés CLR d'un type d'entité à plusieurs tables de la base de données
(fractionnement d'entités)
Le fractionnement d'entités permet aux propriétés d'un type d'entité d'être réparties dans plusieurs
tables. Dans l'exemple suivant, l'entité Department est fractionnée en deux tables : Department et
DepartmentDetails. Le fractionnement d'entités utilise plusieurs appels à la méthode Map pour
mapper un sous-ensemble de propriétés à une table spécifique.
modelBuilder.Entity<Department>()
.Map(m =>
{
m.Properties(t => new { t.DepartmentID, t.Name });
m.ToTable("Department");
})
.Map(m =>
{
m.Properties(t => new { t.DepartmentID, t.Administrator, t.StartDate, t.Budget });
m.ToTable("DepartmentDetails");
});
Mappage de plusieurs types d'entité à une table de la base de données (fractionnement de tables)
L'exemple suivant mappe deux types d'entités qui partagent une clé primaire à une table.
modelBuilder.Entity<OfficeAssignment>()
.HasKey(t => t.InstructorID);
modelBuilder.Entity<Instructor>()
.HasRequired(t => t.OfficeAssignment)
.WithRequiredPrincipal(t => t.Instructor);
modelBuilder.Entity<Instructor>().ToTable("Instructor");
modelBuilder.Entity<OfficeAssignment>().ToTable("Instructor");
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
// add a reference to System.ComponentModel.DataAnnotations DLL
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System;
// Navigation property
public virtual ICollection<Course> Courses { get; private set; }
}
public class Course
{
public Course()
{
this.Instructors = new HashSet<Instructor>();
}
// Primary key
public int CourseID { get; set; }
// Foreign key
public int DepartmentID { get; set; }
// Navigation properties
public virtual Department Department { get; set; }
public virtual ICollection<Instructor> Instructors { get; private set; }
}
// Primary key
public int InstructorID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public System.DateTime HireDate { get; set; }
// Navigation properties
public virtual ICollection<Course> Courses { get; private set; }
}
// Navigation property
public virtual Instructor Instructor { get; set; }
}
Introduction
Quand vous configurez les relations avec le fluent API, vous commencez avec l’instance
EntityTypeConfiguration ensuite vous utilisez les méthodes HasRequired, HasOptional, HasMany
pour spécifier le type des relations.
Les méthodes HasRequired et HasOptional prennent comme argument une expression Lambda qui
représente une reference vers une propriété de navigation
La méthode HasMany prend en argument une expression lambda qui représente une collection de
propriété de navigation. Vous pouvez configurer une propriété inverse en utilisant les méthodes
WithRequired, WithOptional, WithMany. Les méthodes ont une surcharge qui ne prend pas
d’arguments qui peut être utilisé pour une cardinalité unidirectionnelle.
Vous pouvez également configurer les propriétés relatives aux clefs étrangères en utilisant les
méthodes HasForeignKey. Cette méthode prend comme argument une expression lambda qui
représente la propriété utilisé comme clef étrangère.
Comme le nom de la propriété ne respecte pas la convention, la méthode HasKey est utilisée pour
configurer la clé primaire.
Dans la plupart des cas, Entity Framework peut déduire quel type est l’association qui est la
principale dans une relation. Toutefois, lorsque les deux extrémités de la relation sont obligatoires ou
les deux côtés sont facultatifs, Entity Framework ne peut pas identifier l’association principale.
Lorsque les deux extrémités de la relation sont nécessaires, utiliser WithRequiredPrincipal ou
WithRequiredDependent après la méthode HasRequired. Lorsque les deux extrémités de la relation
sont facultatives, utilisez WithOptionalPrincipal ou WithOptionalDependent après la méthode
HasOptional.
modelBuilder.Entity<Instructor>()
.HasRequired(t => t.OfficeAssignment)
.WithRequiredPrincipal(t => t.Instructor);
Le code suivant configure la relation many-to-many entre Course et Instructor. Dans l’exemple
suivant, Par défaut (convention) le code first va créer une table associative. Le résultat est une table
CourseInstructor avec comme colonnes Course_CourseID et Instructor_InstructorID.
modelBuilder.Entity<Course>()
.HasMany(t => t.Instructors)
.WithMany(t => t.Courses)
modelBuilder.Entity<Course>()
.HasMany(t => t.Instructors)
.WithMany(t => t.Courses)
.Map(m =>
{
m.ToTable("CourseInstructor");
m.MapLeftKey("CourseID");
m.MapRightKey("InstructorID");
});
Configuring a Relationship with One Navigation Property
Une relation unidirectionnelle est definie quand une propriété de navigation est présente
uniquement dans un bout de la relation.
Par convention, Code first l’interprète une relation unidirectionnel comme étant une relation one to
many.
Si vous voulez par exemple configurer une relation one-to-one entre Instructor et OfficeAssignment,
avec une propriété de navigation présente uniquement du coté Instructor, vous devez utiliser Fluent
API.
modelBuilder.Entity<Instructor>()
.HasRequired(t => t.OfficeAssignment)
.WithRequiredPrincipal();
Vous pouvez configurer une suppression en cascade sur une relation en utilisant la méthode
WillCascadeOnDelete. Si une clé étrangère sur l'entité dépendante n’est pas nullable, Entity
Framework active la suppression en cascade par défaut. Si une clé étrangère sur l'entité dépendante
est nullable Entity Framework désactive la suppression en cascade sur la relation, et lorsque le
principal est supprimé la clé étrangère sera définie sur null.
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>()
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>()
modelBuilder.Entity<Course>()
.HasRequired(t => t.Department)
.WithMany(t => t.Courses)
.HasForeignKey(d => d.DepartmentID)
.WillCascadeOnDelete(false);
Si la clé primaire du type Département est composée des propriétés DepartmentID et Nom, vous
pouvez configurer la clé primaire pour le Ministère et la clé étrangère sur les types de cours comme
suit :
Si vous choisissez de ne pas définir une clé étrangère sur le type de CLR, mais souhaitez spécifier quel
nom il devrait avoir dans la base de données, procédez comme suit:
modelBuilder.Entity<Course>()
.HasRequired(c => c.Department)
.WithMany(t => t.Courses)
.Map(m => m.MapKey("ChangedDepartmentID"));
Configuring a Foreign Key Name That Does Not Follow the Code First Convention
Si la propriété de clé étrangère sur la classe du cours a été appelé SomeDepartmentID lieu de
DepartmentID, vous devez faire ce qui suit pour indiquer que vous voulez SomeDepartmentID d'être
la clé étrangère :
modelBuilder.Entity<Course>()
.HasRequired(c => c.Department)
.WithMany(d => d.Courses)
.HasForeignKey(c => c.SomeDepartmentID);
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
// add a reference to System.ComponentModel.DataAnnotations DLL
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System;
// Navigation property
public virtual ICollection<Course> Courses { get; private set; }
}
// Foreign key
public int DepartmentID { get; set; }
// Navigation properties
public virtual Department Department { get; set; }
public virtual ICollection<Instructor> Instructors { get; private set; }
}
// Primary key
public int InstructorID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public System.DateTime HireDate { get; set; }
// Navigation properties
public virtual ICollection<Course> Courses { get; private set; }
}
// Navigation property
public virtual Instructor Instructor { get; set; }
}
Database Initialization Strategies in Code-First:
Vous avez déjà créé une base de données après l'exécution de votre application code-
First la première fois, mais que dire de la deuxième fois?? Est-ce qu’il va créer une
nouvelle base à chaque fois que vous exécutez l’application ? Comment va se
comporter dans un environnement de production ? Comment vous allez modifier la
base de données lorsque vous modifiez votre modèle de domaine? Pour gérer ces
scénarios, vous devez utiliser l'une des stratégies d'initialisation de base de données.
Pour utiliser l'une des stratégies DB d'initialisation ci-dessus, vous devez régler le DB
Initializer utilisant la classe de base de données dans la classe de contexte, comme
indiqué ci-dessous:
You can also create your custom DB initializer, by inheriting one of the initializers, as
shown below:
Comme vous pouvez le voir dans le code ci-dessus, nous avons créé une nouvelle
SchoolDBInitializer de classe, qui est dérivé de CreateDatabaseIfNotExists
initializer.
value="System.Data.Entity.DropCreateDatabaseAlways`1[[SchoolDataLaye
r.SchoolDBContext, SchoolDataLayer]], EntityFramework" />
</appSettings>
</configuration>
Vous pouvez implémenter la DB initializer personnalisé, comme suit :
Migration
Cette procédure pas à pas fournit une vue d'ensemble de Migrations Code First dans Entity
Framework. Exécutez la procédure complète ou passez à la rubrique qui vous intéresse. Les rubriques
suivantes sont traitées :
Activation de migrations
Personnalisation de migrations
Avant de commencer à utiliser les migrations, vous devez disposer d'un projet et d'un modèle Code
First. Pour cette procédure pas à pas, vous allez utiliser le modèle canonique Blog et Post.
using System.Data.Entity;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity.Infrastructure;
namespace MigrationsDemo
{
public class BlogContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
}
Vous disposez à présent d'un modèle et il est temps de l'utiliser pour accéder aux données.
Mettez à jour le fichier Programl.cs avec le code indiqué ci-dessous.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MigrationsDemo
{
class Program
{
static void Main(string[] args)
{
using (var db = new BlogContext())
{
db.Blogs.Add(new Blog { Name = "Another Blog " });
db.SaveChanges();
Si SQL Express est installé (fourni dans Visual Studio 2010), la base de données est créée sur
votre instance SQL Express locale (.\SQLEXPRESS). Si SQL Express n'est pas installé, Code First
utilise LocalDb ((localdb)\v11.0) - LocalDb est inclus dans Visual Studio 2012.
Remarque : l'instance SQL Express est toujours prioritaire si elle est installée, même si vous
utilisez Visual Studio 2012.
Activation de migrations
Si vous réexécutez l'application, vous obtenez une exception InvalidOperationException qui indique
que le modèle de sauvegarde du contexte « BlogContext » a changé depuis la création de la base de
données. Envisagez d'utiliser Migrations Code First pour mettre à jour la base de données
( http://go.microsoft.com/fwlink/?LinkId=238269).
Comme le suggère l'exception, il est temps de commencer à utiliser Migrations Code First. La
première étape consiste à activer les migrations pour votre contexte.
Cette commande a ajouté un dossier Migrations à votre projet. Ce nouveau dossier contient deux
fichiers :
Une migration InitialCreate. Cette migration a été générée, car Code First a déjà créé une
base de données pour vous, avant que les migrations ne soient activées. Le code de cette
migration créée à l'aide de la génération de modèle automatique représente les objets qui
ont déjà été créés dans la base de données. Dans ce cas, il s'agit de la table Blog avec les
colonnes BlogId et Name. Le nom de fichier contient un horodateur pour faciliter le tri.
Si la base de données n'a pas été créée, cette migration InitialCreate n'est pas ajoutée au
projet. En revanche, la première fois que vous appelez Migration, le code pour créer ces tables
est créé à l'aide du modèle de génération automatique dans une nouvelle migration.
Migrations Code First dispose de deux commandes principales avec lesquelles vous allez vous
familiariser.
Vous devez générer un modèle automatique de migration pour prendre soin de la nouvelle propriété
URL que vous avez ajoutée. La commande Add-Migrationvous permet de donner un nom à ces
migrations. Appelez les vôtres AddBlogUrl.
namespace MigrationsDemo.Migrations
{
using System;
using System.Data.Entity.Migrations;
Modifiez ou ajoutez du code à cette migration, mais elle semble plutôt satisfaisante. Utilisez Update-
Database pour appliquer cette migration à la base de données.
Migrations Code First va comparer les migrations du dossier Migrations avec celles qui ont
été appliquées à la base de données. L'application va détecter que la
migration AddBlogUrl doit être appliquée et l'exécute.
Personnalisation de migrations
Jusqu'à présent vous avez créé et exécuté une migration sans apporter de modifications. Observez
comment modifier le code généré par défaut.
Il est temps d'apporter des modifications à votre modèle. Ajoutez une propriété Rating à la
classe Blog.
Vous allez utiliser la commande Add-Migration pour laisser Migrations Code First générer un modèle
automatique de migration pour vous. Appelez cette migration AddPostClass.
Migrations Code First Code a bien traité ces modifications, mais vous souhaitez apporter d'autres
modifications :
2. Vous ajoutez également une colonne Blogs.Rating non-nullable. S'il existe des données dans
la table, la valeur par défaut CLR du type de données de la nouvelle colonne leur sera
affectée, (Rating est de type entier, la valeur sera donc 0). Mais vous souhaitez spécifier une
valeur par défaut de 3 afin que les lignes existantes dans la table Blogs commencent par une
évaluation convenable.
(La valeur par défaut est spécifiée sur la ligne 24 du code ci-dessous)
namespace MigrationsCodeDemo.Migrations
{
using System;
using System.Data.Entity.Migrations;
La migration modifiée est prête. Utilisez la commande Update-Database pour mettre la base de
données à jour. Cette fois, spécifiez l'indicateur –Verbose afin que vous puissiez voir le SQL exécuté
par Migrations Code First.
Jusqu'à présent vous avez examiné les opérations de migration qui ne modifient pas ou ne déplacent
pas les données. Observez maintenant une opération qui doit déplacer des données. Il n'y a aucune
prise en charge native pour le déplacement des données, mais vous pouvez exécuter des
commandes SQL arbitraires à tout moment dans le script.
Ajoutez une propriété Post.Abstract à votre modèle. Ultérieurement, vous allez préremplir la
colonne Abstract des publications existantes à l'aide du texte de la colonne Content.
Vous allez utiliser la commande Add-Migration pour laisser Migrations Code First générer un modèle
automatique de migration pour vous.
La migration générée traite les modifications du schéma, mais vous souhaitez également
préremplir la colonne Abstract à l'aide des 100 premiers caractères du contenu de chaque
publication. Pour cela, passez au langage SQL et exécutez une instruction UPDATE après
avoir ajouté la colonne.
(Ajout à la ligne 12 dans le code ci-dessous).
namespace MigrationsCodeDemo.Migrations
{
using System;
using System.Data.Entity.Migrations;
La migration modifiée est satisfaisante. Utilisez la commande Update-Database pour mettre la base
de données à jour. Spécifiez l'indicateur – Verbose afin d'afficher l'instruction SQL exécutée sur la
base de données.
Jusqu'à présent vous avez toujours procédé à la mise à niveau vers la migration la plus récente, mais
il peut arriver que vous souhaitiez procéder à une mise à niveau ou à une mise à niveau vers une
version antérieure d'une migration spécifique.
Supposons que vous souhaitez migrer votre base de données dans l'état où elle se trouvait après
exécution de la migration AddBlogUrl. Utilisez le commutateur –TargetMigration pour mettre à
niveau vers la version antérieure de cette migration.
Si vous souhaitez restaurer une base de données vide, utilisez la commande Update-Database –
TargetMigration: $InitialDatabase.
Si un autre développeur souhaite appliquer ces modifications sur son ordinateur, il peut effectuer
une synchronisation après que vous avez vérifié les modifications dans le contrôle de code source.
Une fois qu'il dispose de vos nouvelles migrations, il lui suffit d'exécuter la commande Update-
Database pour appliquer les modifications localement. Toutefois si vous souhaitez envoyer ces
modifications sur un serveur de test, et par la suite en production, vous souhaiterez probablement
un script SQL qui peut être fourni à votre administrateur de base de données.
Exécutez la commande Update-Database, mais cette fois spécifiez l'indicateur – Script afin
que les modifications soient écrites dans un script et non pas appliquées. Vous allez aussi
spécifier une migration source et une migration cible pour lesquelles le script doit être
généré. Vous souhaitez un script pour passer d'une base de données vide ($InitialDatabase)
à la version la plus récente (migration AddPostAbstract).
Si vous ne spécifiez pas de migration cible, Migrations va utiliser la dernière migration en tant
que cible. Si vous ne spécifiez pas de migration source, Migrations va utiliser l'état actif de la
base de données.
Migrations Code First va exécuter le pipeline de migrations, mais au lieu d'appliquer les
modifications, il les écrit dans un fichier .sql. Une fois le script généré, il s'ouvre dans Visual Studio,
prêt à être consulté ou enregistré.
Si vous déployez votre application, vous pouvez souhaiter qu'il mette automatiquement à niveau la
base de données (en appliquant les migrations en attente) au démarrage de l'application. Pour cela,
inscrivez l'initialiseur de base de données MigrateDatabaseToLatestVersion. Un initialiseur de base
de données contient simplement la logique utilisée pour vérifier que la base de données est installée
correctement. Cette logique s'exécute lors de la première utilisation du contexte dans le processus
d'application (AppDomain).
Lorsque vous créez une instance de l'initialiseur, vous devez spécifier le type de contexte
(BlogContext) et la configuration des migrations (Configuration) ; la configuration des migrations
correspond à la classe qui a été ajoutée au dossier Migrations lorsque vous avez activé les migrations.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using MigrationsDemo.Migrations;
namespace MigrationsDemo
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<BlogContext, Configuration>());
Désormais, chaque fois que votre application s'exécute, elle vérifie si la base de données cible est à
jour et applique les migrations en attente dans le cas contraire.
Résumé
Dans cette procédure pas à pas vous avez appris comment générer des modèles automatiques,
modifier et exécuter des migrations basées sur le code pour mettre à niveau ou mettre à niveau vers
une version antérieure votre base de données. Vous avez également appris comment obtenir un
script SQL pour appliquer des migrations à une base de données et comment appliquer
automatiquement les migrations en attente au démarrage de l'application.
REFERENCE
https://msdn.microsoft.com/fr-fr/data/ee712907