API Fluent - Configuration et mappage des propriétés et des types

Lorsque vous utilisez Entity Framework Code First, le comportement par défaut consiste à mapper vos classes POCO à des tables à l’aide d’un ensemble de conventions intégrées à EF. Toutefois, parfois, vous ne pouvez pas ou ne souhaitez pas suivre ces conventions et avoir besoin de mapper des entités à quelque chose d’autre que ce que les conventions dictent.

Il existe deux façons principales de configurer EF pour utiliser quelque chose d’autre que des conventions, à savoir annotations ou l’API Fluent des EFs. Les annotations couvrent uniquement un sous-ensemble des fonctionnalités de l’API Fluent. Il existe donc des scénarios de mappage qui ne peuvent pas être réalisés à l’aide d’annotations. Cet article est conçu pour montrer comment utiliser l’API Fluent pour configurer des propriétés.

L’API Fluent de code est la plus couramment accessible en remplaçant la méthode OnModelCreating sur votre DbContext dérivée. Les exemples suivants sont conçus pour montrer comment effectuer différentes tâches avec l’API Fluent et vous permettent de copier le code et de le personnaliser en fonction de votre modèle, si vous souhaitez voir le modèle qu’il peut être utilisé en l’état, il est fourni à la fin de cet article.

Paramètres à l’échelle du modèle

Schéma par défaut (EF6 et versions ultérieures)

À compter d’EF6, vous pouvez utiliser la méthode HasDefaultSchema sur DbModelBuilder pour spécifier le schéma de base de données à utiliser pour toutes les tables, procédures stockées, etc. Ce paramètre par défaut est substitué pour tous les objets pour utilisant explicitement un schéma différent.

modelBuilder.HasDefaultSchema("sales");

Conventions personnalisées (EF6 et versions ultérieures)

À compter d’EF6, vous pouvez créer vos propres conventions pour compléter celles incluses dans Code First. Pour plus d’informations, consultez Conventions Code First personnalisées.

Mappage de propriété

La méthode Property est utilisée pour configurer des attributs pour chaque propriété appartenant à une entité ou à un type complexe. La méthode Property est utilisée pour obtenir un objet de configuration pour une propriété donnée. Les options de l’objet de configuration sont spécifiques au type en cours de configuration ; IsUnicode est disponible uniquement sur les propriétés de chaîne, par exemple.

Configuration d’une clé primaire

La convention Entity Framework pour les clés primaires est la suivante :

  1. Votre classe définit une propriété dont le nom est « ID » ou « Id »
  2. ou un nom de classe suivi de l’« ID » ou « Id »

Pour définir explicitement une propriété comme clé primaire, vous pouvez utiliser 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ésactiver l’identité pour les clés primaires numériques

L’exemple suivant définit la propriété DepartmentID sur 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 sur une propriété

Dans l’exemple suivant, la propriété Name ne doit pas dépasser 50 caractères. Si vous définissez la valeur sur plus de 50 caractères, vous obtenez une exception DbEntityValidationException. Si Code First crée une base de données à partir de ce modèle, elle définit également la longueur maximale de la colonne Name sur 50 caractères.

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

Configuration de la propriété pour qu’elle soit obligatoire

Dans l’exemple suivant, la propriété Name est requise. Si vous ne spécifiez pas le nom, vous obtiendrez 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 généralement non nullable.

Remarque

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

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

Configuration d’un index sur une ou plusieurs propriétés

Remarque

EF6.1 et versions ultérieures uniquement : l’attribut Index a été introduit dans Entity Framework 6.1. Si vous utilisez une version antérieure, les informations de cette section ne s’appliquent pas.

La création d’index n’est pas prise en charge en mode natif par l’API Fluent, mais vous pouvez utiliser la prise en charge de IndexAttribute via l’API Fluent. Les attributs d’index sont traités en incluant une annotation de modèle sur le modèle qui est ensuite transformée en index dans la base de données ultérieurement dans le pipeline. Vous pouvez ajouter manuellement ces mêmes annotations à l’aide de l’API Fluent.

La méthode la plus simple consiste à créer une instance de IndexAttribute qui contient tous les paramètres du nouvel index. Vous pouvez ensuite créer une instance de IndexAnnotation qui est un type spécifique EF qui convertit les paramètres IndexAttribute en annotation de modèle qui peut être stockée sur le modèle EF. Ils peuvent ensuite être passés à la méthode HasColumnAnnotation sur l’API Fluent, en spécifiant le nom Index de l’annotation.

modelBuilder
    .Entity<Department>()
    .Property(t => t.Name)
    .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute()));

Pour obtenir la liste complète des paramètres disponibles dans IndexAttribute, consultez la section Index des annotations de Code First Data Annotations. Cela inclut la personnalisation du nom de l’index, la création d’index uniques et la création d’index à plusieurs colonnes.

Vous pouvez spécifier plusieurs annotations d’index sur une propriété unique en passant un tableau de IndexAttribute au constructeur de IndexAnnotation.

modelBuilder
    .Entity<Department>()
    .Property(t => t.Name)
    .HasColumnAnnotation(
        "Index",  
        new IndexAnnotation(new[]
            {
                new IndexAttribute("Index1"),
                new IndexAttribute("Index2") { IsUnique = true }
            })));

Spécification de ne pas mapper une propriété CLR à une colonne dans la base de données

L’exemple suivant montre comment spécifier qu’une propriété sur un type CLR n’est pas mappée à une colonne de 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");

Renommage d’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 que vous souhaitez spécifier le nom qu’il doit 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"));

Configuration de la configuration d’une propriété string prenant en charge le contenu Unicode

Par défaut, les chaînes sont Unicode (nvarchar dans SQL Server). Vous pouvez utiliser 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 de mapper à différentes représentations du même type de base. L’utilisation de cette méthode ne vous permet pas d’effectuer une conversion des données au moment de l’exécution. Notez que IsUnicode est le moyen préféré de définir des colonnes sur varchar, car il s’agit d’une base de données indépendante.

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

Configuration des propriétés sur un type complexe

Il existe deux façons de configurer des propriétés scalaires sur un type complexe.

Vous pouvez appeler Property sur ComplexTypeConfiguration.

modelBuilder.ComplexType<Details>()
    .Property(t => t.Location)
    .HasMaxLength(20);

Vous pouvez également utiliser la notation par points pour accéder à une propriété d’un type complexe.

modelBuilder.Entity<OnsiteCourse>()
    .Property(t => t.Details.Location)
    .HasMaxLength(20);

Configuration d’une propriété à utiliser comme jeton d’accès concurrentiel optimiste

Pour spécifier qu’une propriété dans une entité représente un jeton d’accès concurrentiel, vous pouvez utiliser 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 une version de ligne dans la base de données. La définition de la propriété pour qu’elle soit une version de ligne la configure automatiquement pour qu’elle soit un jeton d’accès concurrentiel optimiste.

modelBuilder.Entity<OfficeAssignment>()
    .Property(t => t.Timestamp)
    .IsRowVersion();

Mappage de type

Spécification d’une classe est un type complexe

Par convention, un type qui n’a aucune clé primaire spécifiée est traité comme un type complexe. Il existe certains scénarios où Code First ne détecte pas un type complexe (par exemple, si vous avez une propriété appelée ID, mais vous ne signifiez pas qu’il s’agit d’une clé primaire). Dans ce cas, vous utiliseriez l’API Fluent pour spécifier explicitement qu’un type est un type complexe.

modelBuilder.ComplexType<Details>();

Spécification de ne pas mapper un type d’entité CLR à une table dans la base de données

L’exemple suivant montre comment exclure un type CLR d’être mappé à une table de la base de données.

modelBuilder.Ignore<OnlineCourse>();

Mappage d’un type d’entité à une table spécifique dans la base de données

Toutes les propriétés du service sont mappées aux colonnes d’une table appelée t_ Department.

modelBuilder.Entity<Department>()  
    .ToTable("t_Department");

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

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

Mappage de l’héritage de table par hiérarchie (TPH)

Dans le scénario de mappage TPH, tous les types d’une hiérarchie d’héritage sont mappés à une seule table. Une colonne de discrimination est utilisée pour identifier le type de chaque ligne. Lors de la création de votre modèle avec Code First, TPH est 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 avec le nom « Discriminator » et le nom de type CLR de chaque type de la hiérarchie est utilisé pour les valeurs de discriminateur. Vous pouvez modifier 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 de table par type (TPT)

Dans le scénario de mappage TPT, 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 joint la table dérivée à la table de base.

modelBuilder.Entity<Course>().ToTable("Course");  
modelBuilder.Entity<OnsiteCourse>().ToTable("OnsiteCourse");

Mappage de l’héritage de la classe table par béton (TPC)

Dans le scénario de mappage TPC, tous les types non abstraits de la hiérarchie sont mappés à des tables individuelles. Les tables qui mappent aux classes dérivées n’ont aucune relation avec la table qui est mappée à la classe de base dans la base de données. Toutes les propriétés d’une classe, y compris 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 héritées de la classe de base aux nouvelles colonnes de la table pour la classe dérivée.

Remarque

Notez que, étant donné que les tables participant à la hiérarchie d’héritage TPC ne partagent pas de clé primaire, il y aura des clés d’entité en double lors de l’insertion dans des tables mappées à des sous-classes si vous avez des valeurs générées par la base de données avec la même valeur initiale d’identité. Pour résoudre ce problème, vous pouvez spécifier une valeur initiale différente pour chaque table ou désactiver l’identité sur la propriété de clé primaire. L’identité est la valeur par défaut pour les propriétés de clé entière lors de l’utilisation de 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 d’un type d’entité à plusieurs tables dans la base de données (fractionnement d’entité)

Le fractionnement d’entité permet aux propriétés d’un type d’entité d’être réparties entre plusieurs tables. Dans l’exemple suivant, l’entité Department est divisé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és à une table dans la base de données (fractionnement de table)

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

Mappage d’un type d’entité pour insérer/mettre à jour/supprimer des procédures stockées (EF6 et versions ultérieures)

À compter d’EF6, vous pouvez mapper une entité pour utiliser des procédures stockées pour insérer une mise à jour et une suppression. Pour plus d’informations, consultez Code First Insert/Update/Delete Stored Procedures.

Modèle utilisé dans des exemples

Le modèle Code First suivant est utilisé pour les exemples de 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 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; }
}