Entity Framework - API Fluent

L'API Fluent est un moyen avancé de spécifier la configuration du modèle qui couvre tout ce que les annotations de données peuvent faire en plus d'une configuration plus avancée impossible avec les annotations de données. Les annotations de données et l'API fluent peuvent être utilisées ensemble, mais Code First donne la priorité à l'API Fluent> annotations de données> conventions par défaut.

  • L'API Fluent est un autre moyen de configurer vos classes de domaine.

  • L'API Code First Fluent est le plus souvent accessible en remplaçant la méthode OnModelCreating sur votre DbContext dérivé.

  • L'API Fluent fournit plus de fonctionnalités pour la configuration que les DataAnnotations. L'API Fluent prend en charge les types de mappages suivants.

Dans ce chapitre, nous continuerons avec l'exemple simple qui contient les classes Student, Course et Enrollment et une classe de contexte avec le nom MyContext comme indiqué dans le code suivant.

using System.Data.Entity; 
using System.Linq; 
using System.Text;
using System.Threading.Tasks;  

namespace EFCodeFirstDemo {

   class Program {
      static void Main(string[] args) {}
   }
   
   public enum Grade {
      A, B, C, D, F
   }

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

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

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

   public class MyContext : DbContext {
      public virtual DbSet<Course> Courses { get; set; }
      public virtual DbSet<Enrollment> Enrollments { get; set; }
      public virtual DbSet<Student> Students { get; set; }
   }

}

Pour accéder à l'API Fluent, vous devez remplacer la méthode OnModelCreating dans DbContext. Jetons un coup d'œil à un exemple simple dans lequel nous renommerons le nom de la colonne dans la table des étudiants de FirstMidName en FirstName, comme indiqué dans le code suivant.

public class MyContext : DbContext {

   protected override void OnModelCreating(DbModelBuilder modelBuilder) {
      modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName)
      .HasColumnName("FirstName");}

      public virtual DbSet<Course> Courses { get; set; }
      public virtual DbSet<Enrollment> Enrollments { get; set; }
      public virtual DbSet<Student> Students { get; set; }
}

DbModelBuilder est utilisé pour mapper les classes CLR à un schéma de base de données. C'est la classe principale et sur laquelle vous pouvez configurer toutes vos classes de domaine. Cette approche centrée sur le code pour créer un modèle de données d'entité (EDM) est connue sous le nom de Code First.

L'API Fluent fournit un certain nombre de méthodes importantes pour configurer les entités et ses propriétés afin de remplacer diverses conventions Code First. Voici quelques-uns d'entre eux.

Sr. No. Nom et description de la méthode
1

ComplexType<TComplexType>

Enregistre un type comme type complexe dans le modèle et renvoie un objet qui peut être utilisé pour configurer le type complexe. Cette méthode peut être appelée plusieurs fois pour le même type pour effectuer plusieurs lignes de configuration.

2

Entity<TEntityType>

Enregistre un type d'entité dans le cadre du modèle et renvoie un objet pouvant être utilisé pour configurer l'entité. Cette méthode peut être appelée plusieurs fois pour qu'une même entité effectue plusieurs lignes de configuration.

3

HasKey<TKey>

Configure la ou les propriétés de clé primaire pour ce type d'entité.

4

HasMany<TTargetEntity>

Configure une relation multiple à partir de ce type d'entité.

5

HasOptional<TTargetEntity>

Configure une relation facultative à partir de ce type d'entité. Les instances du type d'entité pourront être enregistrées dans la base de données sans que cette relation ne soit spécifiée. La clé étrangère dans la base de données sera Nullable.

6

HasRequired<TTargetEntity>

Configure une relation requise à partir de ce type d'entité. Les instances du type d'entité ne pourront pas être enregistrées dans la base de données à moins que cette relation ne soit spécifiée. La clé étrangère dans la base de données sera non nullable.

sept

Ignore<TProperty>

Exclut une propriété du modèle afin qu'elle ne soit pas mappée à la base de données. (Hérité de StructuralTypeConfiguration <TStructuralType>)

8

Property<T>

Configure une propriété struct définie sur ce type. (Hérité de StructuralTypeConfiguration <TStructuralType>)

9

ToTable(String)

Configure le nom de table auquel ce type d'entité est mappé.

L'API Fluent vous permet de configurer vos entités ou leurs propriétés, que vous souhaitiez modifier la manière dont elles sont mappées à la base de données ou comment elles sont liées les unes aux autres. Il existe une grande variété de mappages et de modélisations sur lesquels vous pouvez avoir un impact en utilisant les configurations. Voici les principaux types de mappage pris en charge par Fluent API -

  • Mappage d'entités
  • Cartographie des propriétés

Mappage d'entités

Le mappage d'entité n'est que quelques mappages simples qui auront un impact sur la compréhension d'Entity Framework de la façon dont les classes sont mappées aux bases de données. Nous avons discuté de tout cela dans les annotations de données et nous verrons ici comment réaliser les mêmes choses en utilisant Fluent API.

  • Donc, plutôt que d'aller dans les classes de domaine pour ajouter ces configurations, nous pouvons le faire à l'intérieur du contexte.

  • La première chose à faire est de remplacer la méthode OnModelCreating, qui permet au modelBuilder de travailler avec.

Schéma par défaut

Le schéma par défaut est dbo lorsque la base de données est générée. 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.

Jetons un coup d'œil à l'exemple suivant dans lequel le schéma d'administration est appliqué.

public class MyContext : DbContext {
   public MyContext() : base("name = MyContextDB") {}

   protected override void OnModelCreating(DbModelBuilder modelBuilder) {
      //Configure default schema
      modelBuilder.HasDefaultSchema("Admin");
   }
	
   public virtual DbSet<Course> Courses { get; set; }
   public virtual DbSet<Enrollment> Enrollments { get; set; }
   public virtual DbSet<Student> Students { get; set; }
}

Mapper l'entité à la table

Avec la convention par défaut, Code First créera les tables de base de données avec le nom des propriétés DbSet dans la classe de contexte, telles que Cours, Inscriptions et Etudiants. Mais si vous souhaitez des noms de table différents, vous pouvez remplacer cette convention et fournir un nom de table différent de celui des propriétés DbSet, comme indiqué dans le code suivant.

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   //Map entity to table
   modelBuilder.Entity<Student>().ToTable("StudentData");
   modelBuilder.Entity<Course>().ToTable("CourseDetail");
   modelBuilder.Entity<Enrollment>().ToTable("EnrollmentInfo");
}

Lorsque la base de données est générée, vous verrez le nom des tables tel que spécifié dans la méthode OnModelCreating.

Fractionnement d'entité (mapper l'entité à plusieurs tables)

Le fractionnement d'entités vous permet de combiner des données provenant de plusieurs tables dans une seule classe et il ne peut être utilisé qu'avec des tables qui ont une relation un-à-un entre elles. Jetons un coup d'œil à l'exemple suivant dans lequel les informations sur les étudiants sont mappées dans deux tables.

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   //Map entity to table
   modelBuilder.Entity<Student>().Map(sd ⇒ {
      sd.Properties(p ⇒ new { p.ID, p.FirstMidName, p.LastName });
      sd.ToTable("StudentData");
   })

   .Map(si ⇒ {
      si.Properties(p ⇒ new { p.ID, p.EnrollmentDate });
      si.ToTable("StudentEnrollmentInfo");
   });

   modelBuilder.Entity<Course>().ToTable("CourseDetail");
   modelBuilder.Entity<Enrollment>().ToTable("EnrollmentInfo");
}

Dans le code ci-dessus, vous pouvez voir que l'entité Student est divisée en deux tables suivantes en mappant certaines propriétés à la table StudentData et certaines propriétés à la table StudentEnrollmentInfo à l'aide de la méthode Map.

  • StudentData - Contient le prénom et le nom de l'étudiant.

  • StudentEnrollmentInfo - Contient la date d'inscription.

Lorsque la base de données est générée, vous voyez les tables suivantes dans votre base de données, comme illustré dans l'image suivante.

Cartographie des 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. La méthode Property est utilisée pour obtenir un objet de configuration pour une propriété donnée. Vous pouvez également mapper et configurer les propriétés de vos classes de domaine à l'aide de l'API Fluent.

Configurer une clé primaire

La convention par défaut pour les clés primaires est -

  • La classe définit une propriété dont le nom est «ID» ou «Id»
  • Nom du cours suivi de "ID" ou "Id"

Si votre classe ne suit pas les conventions par défaut pour la clé primaire comme indiqué dans le code suivant de la classe Student -

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

Ensuite, pour définir explicitement une propriété comme clé primaire, vous pouvez utiliser la méthode HasKey comme indiqué dans le code suivant -

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");
	
   // Configure Primary Key
   modelBuilder.Entity<Student>().HasKey<int>(s ⇒ s.StdntID); 
}

Configurer la colonne

Dans Entity Framework, par défaut Code First crée une colonne pour une propriété avec le même nom, ordre et type de données. Mais vous pouvez également remplacer cette convention, comme indiqué dans le code suivant.

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   //Configure EnrollmentDate Column
   modelBuilder.Entity<Student>().Property(p ⇒ p.EnrollmentDate)
	
   .HasColumnName("EnDate")
   .HasColumnType("DateTime")
   .HasColumnOrder(2);
}

Configurer la propriété MaxLength

Dans l'exemple suivant, la propriété Titre du cours ne doit pas dépasser 24 caractères. Lorsque l'utilisateur spécifie une valeur de plus de 24 caractères, l'utilisateur obtient une exception DbEntityValidationException.

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");
   modelBuilder.Entity<Course>().Property(p ⇒ p.Title).HasMaxLength(24);
}

Configurer la propriété Null ou NotNull

Dans l'exemple suivant, la propriété Course Title est obligatoire afin que la méthode IsRequired soit utilisée pour créer la colonne NotNull. De même, Student EnrollmentDate est facultatif, nous utiliserons donc la méthode IsOptional pour autoriser une valeur nulle dans cette colonne, comme indiqué dans le code suivant.

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");
   modelBuilder.Entity<Course>().Property(p ⇒ p.Title).IsRequired();
   modelBuilder.Entity<Student>().Property(p ⇒ p.EnrollmentDate).IsOptional();
	
   //modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName)
   //.HasColumnName("FirstName"); 
}

Configurer les relations

Une relation, dans le contexte des bases de données, est une situation qui existe entre deux tables de base de données relationnelle, lorsqu'une table a une clé étrangère qui fait référence à la clé primaire de l'autre table. Lorsque vous travaillez avec Code First, vous définissez votre modèle en définissant vos classes CLR de domaine. Par défaut, Entity Framework utilise les conventions Code First pour mapper vos classes au schéma de base de données.

  • Si vous utilisez les conventions de dénomination Code First, dans la plupart des cas, vous pouvez compter sur Code First pour configurer des relations entre vos tables en fonction des clés étrangères et des propriétés de navigation.

  • S'ils ne respectent pas ces conventions, vous pouvez également utiliser des configurations pour avoir un impact sur les relations entre les classes et sur la manière dont ces relations sont réalisées dans la base de données lorsque vous ajoutez des configurations dans Code First.

  • Certains d'entre eux sont disponibles dans les annotations de données et vous pouvez en appliquer d'autres encore plus compliqués avec une API Fluent.

Configurer la relation un-à-un

Lorsque vous définissez une relation un-à-un dans votre modèle, vous utilisez une propriété de navigation de référence dans chaque classe. Dans la base de données, les deux tables ne peuvent avoir qu'un seul enregistrement de chaque côté de la relation. Chaque valeur de clé primaire concerne un seul enregistrement (ou aucun enregistrement) dans la table associée.

  • Une relation un-à-un est créée si les deux colonnes associées sont des clés primaires ou ont des contraintes uniques.

  • Dans une relation un-à-un, la clé primaire agit en outre comme une clé étrangère et il n'y a pas de colonne de clé étrangère distincte pour l'une ou l'autre table.

  • Ce type de relation n'est pas courant car la plupart des informations ainsi liées se trouveraient toutes dans un seul tableau.

Jetons un coup d'œil à l'exemple suivant où nous ajouterons une autre classe dans notre modèle pour créer une relation un-à-un.

public class Student {
   public int ID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual StudentLogIn StudentLogIn { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

public class StudentLogIn {
   [Key, ForeignKey("Student")]
   public int ID { get; set; }
   public string EmailID { get; set; }
   public string Password { get; set; }
	
   public virtual Student Student { get; set; }
}

Comme vous pouvez le voir dans le code ci-dessus, les attributs Key et ForeignKey sont utilisés pour la propriété ID dans la classe StudentLogIn, afin de le marquer comme clé primaire ainsi que comme clé étrangère.

Pour configurer une relation un à zéro ou une relation entre Student et StudentLogIn à l'aide de l'API Fluent, vous devez remplacer la méthode OnModelCreating comme indiqué dans le code suivant.

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   // Configure ID as PK for StudentLogIn
   modelBuilder.Entity<StudentLogIn>()
   .HasKey(s ⇒ s.ID);

   // Configure ID as FK for StudentLogIn
   modelBuilder.Entity<Student>()
   
   .HasOptional(s ⇒ s.StudentLogIn) //StudentLogIn is optional
   .WithRequired(t ⇒ t.Student); // Create inverse relationship
}

Dans la plupart des cas, Entity Framework peut déduire quel type est le dépendant et quel est le principal dans une relation. Cependant, lorsque les deux extrémités de la relation sont requises ou que les deux côtés sont facultatifs, Entity Framework ne peut pas identifier la personne à charge et le mandant. Lorsque les deux extrémités de la relation sont requises, vous pouvez utiliser HasRequired comme indiqué dans le code suivant.

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   // Configure ID as PK for StudentLogIn
   modelBuilder.Entity<StudentLogIn>()
   .HasKey(s ⇒ s.ID);

   // Configure ID as FK for StudentLogIn
   modelBuilder.Entity<Student>()
   .HasRequired(r ⇒ r.Student)
   .WithOptional(s ⇒ s.StudentLogIn);  
}

Lorsque la base de données est générée, vous verrez que la relation est créée comme indiqué dans l'image suivante.

Configurer une relation un-à-plusieurs

La table de clé primaire contient un seul enregistrement qui ne concerne aucun, un ou plusieurs enregistrements de la table associée. C'est le type de relation le plus couramment utilisé.

  • Dans ce type de relation, une ligne de la table A peut avoir plusieurs lignes correspondantes dans la table B, mais une ligne de la table B ne peut avoir qu'une seule ligne correspondante dans la table A.

  • La clé étrangère est définie sur la table qui représente la fin plusieurs de la relation.

  • Par exemple, dans le diagramme ci-dessus, les tables Élève et Inscription ont une relation un-à-plusieurs, chaque étudiant peut avoir plusieurs inscriptions, mais chaque inscription appartient à un seul étudiant.

Vous trouverez ci-dessous l'étudiant et l'inscription qui ont une relation un-à-plusieurs, mais la clé étrangère dans la table d'inscription ne suit pas les conventions Code First par défaut.

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
	
   //StdntID is not following code first conventions name
   public int StdntID { get; set; }
   public Grade? Grade { get; set; }
	
   public virtual Course Course { get; set; }
   public virtual Student Student { get; set; }
}

public class Student {
   public int ID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual StudentLogIn StudentLogIn { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Dans ce cas, pour configurer une relation un-à-plusieurs à l'aide de l'API Fluent, vous devez utiliser la méthode HasForeignKey comme indiqué dans le code suivant.

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   //Configure FK for one-to-many relationship
   modelBuilder.Entity<Enrollment>()

   .HasRequired<Student>(s ⇒ s.Student)
   .WithMany(t ⇒ t.Enrollments)
   .HasForeignKey(u ⇒ u.StdntID);  
}

Lorsque la base de données est générée, vous verrez que la relation est créée comme indiqué dans l'image suivante.

Dans l'exemple ci-dessus, la méthode HasRequired spécifie que la propriété de navigation Student doit être Null. Vous devez donc attribuer une entité Étudiant avec inscription à chaque fois que vous ajoutez ou mettez à jour l'inscription. Pour gérer cela, nous devons utiliser la méthode HasOptional au lieu de la méthode HasRequired.

Configurer la relation plusieurs-à-plusieurs

Chaque enregistrement dans les deux tables peut se rapporter à n'importe quel nombre d'enregistrements (ou aucun enregistrement) dans l'autre table.

  • Vous pouvez créer une telle relation en définissant une troisième table, appelée table de jonction, dont la clé primaire est constituée des clés étrangères de la table A et de la table B.

  • Par exemple, la table Student et la table Course ont une relation plusieurs-à-plusieurs.

Voici les classes Etudiant et Cours dans lesquelles Etudiant et Cours ont une relation plusieurs-à-plusieurs, car les deux classes ont des propriétés de navigation Etudiants et Cours qui sont des collections. En d'autres termes, une entité a une autre collection d'entités.

public class Student {
   public int ID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Course> Courses { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

public class Course {
   public int CourseID { get; set; }
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Student> Students { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Pour configurer la relation plusieurs-à-plusieurs entre l'étudiant et le cours, vous pouvez utiliser l'API Fluent comme indiqué dans le code suivant.

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   // Configure many-to-many relationship
   modelBuilder.Entity<Student>()
   .HasMany(s ⇒ s.Courses) 
   .WithMany(s ⇒ s.Students);
}

Les conventions Code First par défaut sont utilisées pour créer une table de jointure lorsque la base de données est générée. Par conséquent, la table StudentCourses est créée avec les colonnes Course_CourseID et Student_ID, comme illustré dans l'image suivante.

Si vous souhaitez spécifier le nom de la table de jointure et les noms des colonnes de la table, vous devez effectuer une configuration supplémentaire à l'aide de la méthode Map.

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   // Configure many-to-many relationship 
   modelBuilder.Entity<Student>()

   .HasMany(s ⇒ s.Courses)
   .WithMany(s ⇒ s.Students)
   
   .Map(m ⇒ {
      m.ToTable("StudentCoursesTable");
      m.MapLeftKey("StudentID");
      m.MapRightKey("CourseID");
   }); 
}

Vous pouvez voir quand la base de données est générée, le nom de la table et des colonnes est créé comme spécifié dans le code ci-dessus.

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