Vous êtes sur la page 1sur 43

EF Code First- Design Pattern

Architecture

GUI Layer :Graphic User Interface

Entities
•Convention
•Data
Annotation
Service Layer •FluentAPI

Data Acesse Layer


Design
Context Migration Configuration
Pattern

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.

public class SchoolEntities : DbContext


{
public DbSet<Department> Departments { get; set; }
}

public class Department


{
// Primary key
public int DepartmentID { get; set; }
public string Name { get; set; }

// Navigation property
public virtual ICollection<Course> Courses { get; set; }
}

public class Course


{
// Primary key
public int CourseID { get; set; }

public string Title { get; set; }


public int Credits { get; set; }

// Foreign key
public int DepartmentID { get; set; }

// Navigation properties
public virtual Department Department { get; set; }
}

public partial class OnlineCourse : Course


{
public string URL { get; set; }
}

public partial class OnsiteCourse : Course


{
public string Location { get; set; }
public string Days { get; set; }
public System.DateTime Time { get; set; }
}

Si vous souhaitez exclure un type du modèle, utilisez l'attribut NotMapped ou l'API


Fluent DbModelBuilder.Ignore.

modelBuilder.Ignore<Department>();

Convention de clé primaire


Code First déduit qu'une propriété est une clé primaire si une propriété sur une classe se nomme
« ID » (ne respectant pas la casse), ou le nom de la classe suivi par « ID ». Si le type de la propriété de
clé primaire est numérique ou GUID, il est configuré en tant que colonne d'identité.

public class Department


{
// Primary key
public int DepartmentID { get; set; }

...

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.

public class Department


{
// Primary key
public int DepartmentID { get; set; }
public string Name { get; set; }

// Navigation property
public virtual ICollection<Course> Courses { get; set; }
}

public class Course


{
// Primary key
public int CourseID { get; set; }

public string Title { get; set; }


public int Credits { 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.

Convention de types complexes


Lorsque Code First découvre une définition de classe dans laquelle une clé primaire ne peut pas être
déduite, et aucune clé primaire n'est enregistrée via des annotations de données ou l'API Fluent, le
type est automatiquement enregistré en tant que type complexe. La détection de type complexe
requiert également que le type n'ait pas de propriétés qui font référence à des types d'entité et ne
soit pas référencé dans une propriété de collection sur un autre type. Selon les définitions de classe
suivantes, Code First déduirait que Details est un type complexe, car il n'a pas de clé primaire.

public partial class OnsiteCourse : Course


{
public OnsiteCourse()
{
Details = new Details();
}
public Details Details { get; set; }
}

public class Details


{
public System.DateTime Time { get; set; }
public string Location { get; set; }
public string Days { get; set; }
}

Convention de chaîne de connexion


a- Utiliser Code First avec une connexion par convention
Si vous n'avez effectué aucune autre configuration dans votre application, l'appel du constructeur
sans paramètre sur DbContext entraîne l'exécution de DbContext en mode Code First avec une
connexion de base de données créée par convention. Par exemple :

namespace Demo.EF

public class BloggingContext : DbContext

public BloggingContext()

// C# will call base class parameterless constructor by default

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 class BloggingContext : DbContext

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 class BloggingContext : DbContext

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 class BloggingContext : DbContext

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.

public class SchoolEntities : DbContext

...
protected override void OnModelCreating(DbModelBuilder modelBuilder)

// Configure Code First to ignore PluralizingTableName convention

// If you keep this convention, the generated tables

// will have pluralized names.

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.

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 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.

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 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.

Voici BloggerName doté de spécifications de longueur. L'exemple montre également comment


combiner des attributs.

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

L'annotation MaxLength aura un impact sur la base de données en affectant la valeur 10 à la


longueur de la propriété.

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.

[MaxLength(10, ErrorMessage="BloggerName doit être composé de 10 caractères maximum"),MinLe


ngth(5)]
public string BloggerName { get; set; }
Vous pouvez également spécifier ErrorMessage dans l'annotation Required.

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.

public class BlogDetails


{
public DateTime? DateCreated { get; set; }

[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.

public BlogDetails BlogDetail { get; set; }

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é.

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

[ConcurrencyCheck, MaxLength (10, ErrorMessage=« BloggerName doit être composé de 10 caractèr


es maximum »),MinLength),(5)]
public string BloggerName { get; set; }
Lorsque SaveChanges est appelé, en raison de l'annotation ConcurrencyCheck sur le champ
BloggerName, la valeur d'origine de cette propriété est utilisée dans la mise à jour. La commande
tente de trouver la ligne appropriée en filtrant non seulement sur la valeur de clé mais également sur
la valeur d'origine de BloggerName. Voici les parties essentielles de la commande UPDATE envoyée à
la base de données, où vous pouvez voir que la commande met à jour la ligne qui a un
PrimaryTrackingKey 1 et un BloggerName « Julie » qui était la valeur d'origine lorsque le blog a été
récupéré dans la base de données.

where (([PrimaryTrackingKey] = @4) and ([BloggerName] = @5))


@4=1,@5=N'Julie'

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.

L'ajout de la propriété suivante à la classe Blog :

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

Ne confondez pas l'attribut TypeName de Column et DataType DataAnnotation. L'annotation


DataType est utilisée pour l'interface utilisateur et est ignorée par Code First.

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.

Attributs de la relation : InverseProperty et ForeignKey


Remarque : cette page fournit des informations sur la définition des relations dans votre modèle Code
First à l'aide d'annotations de données. Pour obtenir des informations d'ordre général sur les relations
dans Entity Framework et sur l'accès aux données et leur manipulation à l'aide de relations,
consultez Propriétés de navigation et des relations.

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.

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.

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.

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

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

L'API Fluent Code First est le plus souvent accessible en remplaçant la


méthode d'OnModelCreating sur votre DbContext dérivé. Les exemples suivants sont conçus pour
illustrer comment effectuer diverses tâches avec l'API Fluent et vous permet de copier le code et de
le personnaliser en fonction de votre modèle. Si vous souhaitez afficher le modèle avec lequel elles
peuvent être utilisées en l'état, celui-ci est fourni à la fin de cet article.

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.

Configuration d'une clé primaire

Convention Entity Framework pour les clés primaires :

1. Votre classe définit une propriété dont le nom est « ID » ou « Id »

2. ou un nom de classe suivi par « ID » ou « Id »

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.

modelBuilder.Entity<OfficeAssignment>().HasKey(t => t.InstructorID);

Configuration d'une clé primaire composite

L'exemple suivant configure les propriétés DepartmentID et Name comme clé primaire composite du
type Department.

modelBuilder.Entity<Department>().HasKey(t => new { t.DepartmentID, t.Name });


Désactivation de l'identité pour les clés primaires numériques

L'exemple suivant définit la propriété DepartmentID à


System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None pour indiquer que la
valeur ne sera pas générée par la base de données.

modelBuilder.Entity<Department>().Property(t => t.DepartmentID)


.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

Spécification de la longueur maximale d'une propriété

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.

modelBuilder.Entity<Department>().Property(t => t.Name).HasMaxLength(50);

Configuration de la propriété pour qu'elle soit de type obligatoire

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.

modelBuilder.Entity<Department>().Property(t => t.Name).IsRequired();

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.

modelBuilder.Entity<Department>().Ignore(t => t.Budget);

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"));

Configuration pour indiquer si la propriété de chaîne prend en charge le contenu Unicode.

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

Configuration du type de données d'une colonne de base de données

La méthode HasColumnType permet le mappage à différentes représentations du même type de


base. Elle ne vous permet pas d'effectuer la conversion des données au moment de l'exécution.
Notez que la méthode IsUnicode est préconisée pour affecter le type varchar aux colonnes, car elle
est indépendante de la base de données.

modelBuilder.Entity<Department>()
.Property(p => p.Name)
.HasColumnType("varchar");

Configuration des propriétés sur un type complexe

Il existe deux manières de configurer les propriétés scalaires sur un type complexe.

Appelez la méthode Property sur ComplexTypeConfiguration.

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

Configuration d'une propriété à utiliser comme jeton de concurrence optimiste

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

Spécifier qu'une classe est un type complexe

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");

Vous pouvez également spécifier le nom de schéma comme suit :

modelBuilder.Entity<Department>()
.ToTable("t_ Department", "school");

Mappage de l'héritage table par hiérarchie

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"));

Mappage de l'héritage table par type

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");

Mappage de l'héritage table par classe concrète

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");

Modèle utilisé dans les exemples


Le modèle Code First suivant est utilisé pour les exemples dans cette page.

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;

public class SchoolEntities : DbContext


{
public DbSet<Course> Courses { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)


{
// Configure Code First to ignore PluralizingTableName convention
// If you keep this convention then the generated tables will have pluralized names.
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}

public class Department


{
public Department()
{
this.Courses = new HashSet<Course>();
}
// Primary key
public int DepartmentID { get; set; }
public string Name { get; set; }
public decimal Budget { get; set; }
public System.DateTime StartDate { get; set; }
public int? Administrator { get; set; }

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

public string Title { get; set; }


public int Credits { 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; }
}

public partial class OnlineCourse : Course


{
public string URL { get; set; }
}

public partial class OnsiteCourse : Course


{
public OnsiteCourse()
{
Details = new Details();
}

public Details Details { get; set; }


}

public class Details


{
public System.DateTime Time { get; set; }
public string Location { get; set; }
public string Days { get; set; }
}

public class Instructor


{
public Instructor()
{
this.Courses = new List<Course>();
}

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

public class OfficeAssignment


{
// Specifying InstructorID as a primary
[Key()]
public Int32 InstructorID { get; set; }

public string Location { get; set; }

// When the Entity Framework sees Timestamp attribute


// it configures ConcurrencyCheck and DatabaseGeneratedPattern=Computed.
[Timestamp]
public Byte[] Timestamp { get; set; }

// Navigation property
public virtual Instructor Instructor { get; set; }
}

Configuration des relations avec l’API Fluent

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.

Configuration de la relation Required-to-Optional (One-to–Zero-or-One)

L’exemple suivant configure une relation one-to-zero-or-one. L’OfficeAssignment a une propriété


InstructorID qui représente une clé primaire et une clé é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.

// Configure the primary key for the OfficeAssignment


modelBuilder.Entity<OfficeAssignment>()
.HasKey(t => t.InstructorID);
// Map one-to-zero or one relationship
modelBuilder.Entity<OfficeAssignment>()
.HasRequired(t => t.Instructor)
.WithOptional(t => t.OfficeAssignment);

Configuration de la relation quand les deux bouts sont requis (One-to-One)

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.

// Configure the primary key for the OfficeAssignment


modelBuilder.Entity<OfficeAssignment>()
.HasKey(t => t.InstructorID);

modelBuilder.Entity<Instructor>()
.HasRequired(t => t.OfficeAssignment)
.WithRequiredPrincipal(t => t.Instructor);

Configuring a Many-to-Many Relationship

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)

Le code suivant sert à modifier le nom et les colonnes de la table associative.

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.

// Configure the primary Key for the OfficeAssignment


modelBuilder.Entity<OfficeAssignment>()
.HasKey(t => t.InstructorID);

modelBuilder.Entity<Instructor>()
.HasRequired(t => t.OfficeAssignment)
.WithRequiredPrincipal();

Enabling Cascade Delete

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.

Vous pouvez supprimer ces conventions suppression en cascade à l’aide:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>()
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>()

Vous pouvez désactiver le cascade on delete à l’aide de ce code:

modelBuilder.Entity<Course>()
.HasRequired(t => t.Department)
.WithMany(t => t.Courses)
.HasForeignKey(d => d.DepartmentID)
.WillCascadeOnDelete(false);

Configuring a Composite Foreign Key

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 :

// Composite primary key


modelBuilder.Entity<Department>()
.HasKey(d => new { d.DepartmentID, d.Name });
// Composite foreign key
modelBuilder.Entity<Course>()
.HasRequired(c => c.Department)
.WithMany(d => d.Courses)
.HasForeignKey(d => new { d.DepartmentID, d.DepartmentName });

Renaming a Foreign Key That Is Not Defined in the Model

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

Model Used in Samples


The following Code First model is used for the samples on this page.

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;

public class SchoolEntities : DbContext


{
public DbSet<Course> Courses { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)


{
// Configure Code First to ignore PluralizingTableName convention
// If you keep this convention then the generated tables will have pluralized names.
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}

public class Department


{
public Department()
{
this.Courses = new HashSet<Course>();
}
// Primary key
public int DepartmentID { get; set; }
public string Name { get; set; }
public decimal Budget { get; set; }
public System.DateTime StartDate { get; set; }
public int? Administrator { get; set; }

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

public string Title { get; set; }


public int Credits { 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; }
}

public partial class OnlineCourse : Course


{
public string URL { get; set; }
}

public partial class OnsiteCourse : Course


{
public OnsiteCourse()
{
Details = new Details();
}

public Details Details { get; set; }


}
public class Details
{
public System.DateTime Time { get; set; }
public string Location { get; set; }
public string Days { get; set; }
}

public class Instructor


{
public Instructor()
{
this.Courses = new List<Course>();
}

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

public class OfficeAssignment


{
// Specifying InstructorID as a primary
[Key()]
public Int32 InstructorID { get; set; }

public string Location { get; set; }

// When the Entity Framework sees Timestamp attribute


// it configures ConcurrencyCheck and DatabaseGeneratedPattern=Computed.
[Timestamp]
public Byte[] Timestamp { get; 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.

Il ya quatre stratégies d'initialisation de base de données:

1. CreateDatabaseIfNotExists: Ceci est initialisation par défaut. Comme son nom


l'indique, il va créer la base de données si aucune n’existe selon la configuration.
Cependant, si vous changez la classe de modèle, puis exécutez l'application de cette
initialisation, puis il va lancer une exception.

2. DropCreateDatabaseIfModelChanges: Cette initialisation efface une base existante


et crée une nouvelle base de données, si vos classes de modèle (classes d'entités) ont
été modifiés. Donc, vous ne devez pas vous inquiéter au sujet de maintenir votre
schéma de base de données, lorsque vos classes de modèles changent.

3. DropCreateDatabaseAlways: Comme son nom l'indique, cette initialisation efface


une base de données chaque fois que vous exécutez l'application existante,
indépendamment de vos classes de modèles ont changé ou non. Cela sera utile, quand
vous voulez nouvelle base de données, chaque fois que vous exécutez l'application,
comme lorsque vous développez l'application.

4. personnalisée DB Initializer: Vous pouvez aussi créer votre propre initialisation


personnalisée, si l'un des ci-dessus, ne répond pas à vos besoins ou que vous voulez
faire un autre processus qui initialise la base de données en utilisant l'initialisation ci-
dessus.

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:

public class SchoolDBContext: DbContext


{

public SchoolDBContext(): base("SchoolDBConnectionString")


{
Database.SetInitializer<SchoolDBContext>(new
CreateDatabaseIfNotExists<SchoolDBContext>());
//Database.SetInitializer<SchoolDBContext>(new
DropCreateDatabaseIfModelChanges<SchoolDBContext>());
//Database.SetInitializer<SchoolDBContext>(new
DropCreateDatabaseAlways<SchoolDBContext>());
//Database.SetInitializer<SchoolDBContext>(new
SchoolDBInitializer());
}
public DbSet<Student> Students { get; set; }
public DbSet<Standard> Standards { get; set; }
}

You can also create your custom DB initializer, by inheriting one of the initializers, as
shown below:

public class SchoolDBInitializer :


CreateDatabaseIfNotExists<SchoolDBContext>
{
protected override void Seed(SchoolDBContext context)
{
base.Seed(context);
}
}

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.

Définissez db initializer dans le fichier de configuration:

Vous pouvez également définir db initializer dans le fichier de configuration. Par


exemple, pour définir initializer par défaut dans app.config:

<?xml version="1.0" encoding="utf-8" ?>


<configuration>
<appSettings>
<add key="DatabaseInitializerForType
SchoolDataLayer.SchoolDBContext, SchoolDataLayer"

value="System.Data.Entity.DropCreateDatabaseAlways`1[[SchoolDataLaye
r.SchoolDBContext, SchoolDataLayer]], EntityFramework" />
</appSettings>
</configuration>
Vous pouvez implémenter la DB initializer personnalisé, comme suit :

<?xml version="1.0" encoding="utf-8" ?>


<configuration>
<appSettings>
<add key="DatabaseInitializerForType
SchoolDataLayer.SchoolDBContext, SchoolDataLayer"
value="SchoolDataLayer.SchoolDBInitializer,
SchoolDataLayer" />
</appSettings>
</configuration>

De cette façon, vous pouvez utiliser la stratégie d'initialisation DB pour votre


application.

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

 Génération et exécution de migrations

 Personnalisation de migrations

 Déplacement de données et SQL personnalisé

 Migration vers une version spécifique (niveau antérieur compris)

 Génération d'un script SQL

 Mise à niveau automatique au démarrage de l'application (initialiseur


MigrateDatabaseToLatestVersion)

Création d'un modèle initial et d'une base de données

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.

 Créez une nouvelle application console MigrationsDemo.

 Ajoutez la version la plus récente du package NuGet EntityFramework au projet.

o Outils -> Gestionnaire de package de bibliothèque -> Console Gestionnaire de


package

o Exécutez la commande Install-Package EntityFramework.


 Ajoutez un fichier Model.cs avec le code indiqué ci-dessous. Ce code définit une
classe Blog constituant votre modèle de domaine et une classe BlogContextqui correspond
au contexte Code First Entity Framework.

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

public class Blog


{
public int BlogId { get; set; }
public string Name { 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();

foreach (var blog in db.Blogs)


{
Console.WriteLine(blog.Name);
}
}

Console.WriteLine("Press any key to exit...");


Console.ReadKey();
}
}
}

 Exécutez l'application et vous constaterez qu'une base de


données MigrationsCodeDemo.BlogContext est créée pour vous.

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.

(Base de données LocalDb)

(Base de données SQL Express)

Activation de migrations

Il est temps d'apporter d'autres modifications à votre modèle.

 Introduisez une propriété URL dans la classe Blog.


public string Url { get; set; }

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.

 Exécutez la commande Enable-Migrations dans la console du gestionnaire de package.

Cette commande a ajouté un dossier Migrations à votre projet. Ce nouveau dossier contient deux
fichiers :

 La classe Configuration. Cette classe vous permet de configurer le comportement de


Migrations pour votre contexte. Pour cette procédure pas à pas, vous allez utiliser la
configuration par défaut.
Étant donné qu'il n'y a qu'un seul contexte Code First dans votre projet, la commande Enable-
Migrations a renseigné le type de contexte auquel cette configuration s'applique.

 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.

Génération et exécution de migrations

Migrations Code First dispose de deux commandes principales avec lesquelles vous allez vous
familiariser.

 Add-Migration génère un modèle automatique de la migration suivante en fonction des


modifications apportées à votre modèle depuis la création de la dernière migration.

 Update-Database applique les migrations en attente à la base de données.

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.

 Exécutez la commande Add-Migration AddBlogUrl dans la console du gestionnaire de


package.

 Le dossier Migrations contient une nouvelle migration AddBlogUrl. Le nom de fichier de


migration contient le préfixe d'un horodateur pour faciliter le tri.

namespace MigrationsDemo.Migrations
{
using System;
using System.Data.Entity.Migrations;

public partial class AddBlogUrl : DbMigration


{
public override void Up()
{
AddColumn("Blogs", "Url", c => c.String());
}

public override void Down()


{
DropColumn("Blogs", "Url");
}
}
}

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.

 Exécutez la commande Update-Database dans la console du gestionnaire de package.

 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.

La base de données MigrationsDemo.BlogContext est maintenant mise à jour pour inclure la


colonne Url dans la table Blogs.

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.

public int Rating { get; set; }

 Ajoutez également une classe Post.

public class Post


{
public int PostId { get; set; }
[MaxLength(200)]
public string Title { get; set; }
public string Content { get; set; }

public int BlogId { get; set; }


public Blog Blog { get; set; }
}
 Vous allez également ajouter une collection Posts à la classe Blog pour former l'autre
extrémité de la relation entre Blog et Post.

public virtual List<Post> Posts { get; set; }

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.

 Exécutez la commande Add-Migration AddPostClass dans la console du gestionnaire de


package.

Migrations Code First Code a bien traité ces modifications, mais vous souhaitez apporter d'autres
modifications :

1. Ajoutez un index unique sur la colonne Posts.Title


(Ajout aux lignes 22 et 29 dans le code ci-dessous).

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;

public partial class AddPostClass : DbMigration


{
public override void Up()
{
CreateTable(
"Posts",
c => new
{
PostId = c.Int(nullable: false, identity: true),
Title = c.String(maxLength: 200),
Content = c.String(),
BlogId = c.Int(nullable: false),
})
.PrimaryKey(t => t.PostId)
.ForeignKey("Blogs", t => t.BlogId, cascadeDelete: true)
.Index(t => t.BlogId)
.Index(p => p.Title, unique: true);

AddColumn("Blogs", "Rating", c => c.Int(nullable: false, defaultValue: 3));


}

public override void Down()


{
DropIndex("Posts", new[] { "Title" });
DropIndex("Posts", new[] { "BlogId" });
DropForeignKey("Posts", "BlogId", "Blogs");
DropColumn("Blogs", "Rating");
DropTable("Posts");
}
}
}

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.

 Exécutez la commande Update-Database –Verbose dans la console du gestionnaire de


package.

Déplacement de données/SQL personnalisé

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.

public string Abstract { get; set; }

Vous allez utiliser la commande Add-Migration pour laisser Migrations Code First générer un modèle
automatique de migration pour vous.

 Exécutez la commande Add-Migration AddPostAbstract dans la console du gestionnaire de


package.

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

public partial class AddPostAbstract : DbMigration


{
public override void Up()
{
AddColumn("Posts", "Abstract", c => c.String());
Sql("UPDATE Posts SET Abstract = LEFT(Content, 100) WHERE Abstract IS NULL");
}

public override void Down()


{
DropColumn("Posts", "Abstract");
}
}
}

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.

 Exécutez la commande Update-Database –Verbose dans la console du gestionnaire de


package.

Migration vers une version spécifique (niveau antérieur compris)

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.

 Exécutez la commande Update-Database –TargetMigration: AddBlogUrl dans la console du


gestionnaire de package.

Cette commande exécute le script Down des migrations AddBlogAbstract et AddPostClass.

Si vous souhaitez restaurer une base de données vide, utilisez la commande Update-Database –
TargetMigration: $InitialDatabase.

Obtention d'un script SQL

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.

 Exécutez la commande Update-Database -Script -SourceMigration: $InitialDatabase -


TargetMigration: AddPostAbstract dans la console du gestionnaire de package.

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é.

Mise à niveau automatique au démarrage de l'application (initialiseur


MigrateDatabaseToLatestVersion)

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).

Mettez à jour le fichier Program.cs, de la façon indiquée ci-dessous, pour définir


l'initialiseur MigrateDatabaseToLatestVersion pour BlogContext avant d'utiliser le contexte
(ligne 14). Notez que vous devez également ajouter une instruction using pour l'espace de
noms System.Data.Entity (ligne 5).

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>());

using (var db = new BlogContext())


{
db.Blogs.Add(new Blog { Name = "Another Blog " });
db.SaveChanges();
foreach (var blog in db.Blogs)
{
Console.WriteLine(blog.Name);
}
}

Console.WriteLine("Press any key to exit...");


Console.ReadKey();
}
}
}

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

Vous aimerez peut-être aussi