📜  Entity Framework-Fluent API

📅  最后修改于: 2020-11-21 07:35:32             🧑  作者: Mango


Fluent API是一种指定模型配置的高级方式,除了可以使用数据批注进行的一些更高级的配置之外,它还涵盖了数据批注可以执行的所有操作。数据注释和fluent API可以一起使用,但是Code First优先使用Fluent API>数据注释>默认约定。

  • Fluent API是配置域类的另一种方法。

  • 通过覆盖派生的DbContext上的OnModelCreating方法,通常可以访问Code First Fluent API。

  • Fluent API提供了比DataAnnotations更多的配置功能。 Fluent API支持以下类型的映射。

在本章中,我们将继续简单的示例,其中包含Student,Course和Enrollment类以及一个具有MyContext名称的上下文类,如以下代码所示。

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 Enrollments { get; set; }
   }

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

   public class MyContext : DbContext {
      public virtual DbSet Courses { get; set; }
      public virtual DbSet Enrollments { get; set; }
      public virtual DbSet Students { get; set; }
   }

}      

要访问Fluent API,您需要在DbContext中重写OnModelCreating方法。让我们看一个简单的示例,在该示例中,我们将学生表中的列名从FirstMidName重命名为FirstName,如以下代码所示。

public class MyContext : DbContext {

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

      public virtual DbSet Courses { get; set; }
      public virtual DbSet Enrollments { get; set; }
      public virtual DbSet Students { get; set; }
}

DbModelBuilder用于将CLR类映射到数据库模式。它是主要类,您可以在其上配置所有域类。这种以代码为中心的构建实体数据模型(EDM)的方法称为代码优先。

Fluent API提供了许多重要的方法来配置实体及其属性,以覆盖各种Code First约定。以下是其中一些。

Sr. No. Method Name & Description
1

ComplexType

Registers a type as a complex type in the model and returns an object that can be used to configure the complex type. This method can be called multiple times for the same type to perform multiple lines of configuration.

2

Entity

Registers an entity type as part of the model and returns an object that can be used to configure the entity. This method can be called multiple times for the same entity to perform multiple lines of configuration.

3

HasKey

Configures the primary key property(s) for this entity type.

4

HasMany

Configures a many relationship from this entity type.

5

HasOptional

Configures an optional relationship from this entity type. Instances of the entity type will be able to be saved to the database without this relationship being specified. The foreign key in the database will be nullable.

6

HasRequired

Configures a required relationship from this entity type. Instances of the entity type will not be able to be saved to the database unless this relationship is specified. The foreign key in the database will be non-nullable.

7

Ignore

Excludes a property from the model so that it will not be mapped to the database. (Inherited from StructuralTypeConfiguration)

8

Property

Configures a struct property that is defined on this type. (Inherited from StructuralTypeConfiguration)

9

ToTable(String)

Configures the table name that this entity type is mapped to.

通过Fluent API,您可以配置实体或实体的属性,无论您想更改有关它们如何映射到数据库或如何相互关联的信息。使用配置可能会影响到各种各样的映射和建模。以下是Fluent API支持的主要映射类型-

  • 实体映射
  • 属性映射

实体映射

实体映射只是一些简单的映射,它将影响实体框架对类如何映射到数据库的理解。我们在数据注释中讨论了所有这些内容,在这里我们将看到如何使用Fluent API实现相同的功能。

  • 因此,我们不必在域类中添加这些配置,而是可以在上下文内部执行此操作。

  • 第一件事是重写OnModelCreating方法,该方法使modelBuilder可以使用。

默认架构

生成数据库时,默认架构为dbo。您可以在DbModelBuilder上使用HasDefaultSchema方法来指定要用于所有表,存储过程等的数据库架构。

让我们看一下下面的示例,其中应用了管理架构。

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

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

将实体映射到表

按照默认约定,Code First将在上下文类(如“课程”,“注册”和“学生”)中使用名称为DbSet的属性创建数据库表。但是,如果要使用不同的表名,则可以覆盖此约定,并且可以提供与DbSet属性不同的表名,如下面的代码所示。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

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

   //Map entity to table
   modelBuilder.Entity().ToTable("StudentData");
   modelBuilder.Entity().ToTable("CourseDetail");
   modelBuilder.Entity().ToTable("EnrollmentInfo");
}

生成数据库后,您将看到在OnModelCreating方法中指定的表名。

OnModel方法

实体拆分(将实体映射到多个表)

使用实体拆分,您可以将来自多个表的数据合并到一个类中,并且只能与它们之间具有一对一关系的表一起使用。让我们看下面的示例,其中将Student信息映射到两个表中。

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

   //Map entity to table
   modelBuilder.Entity().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().ToTable("CourseDetail");
   modelBuilder.Entity().ToTable("EnrollmentInfo");
}

在上面的代码中,通过使用Map方法将某些属性映射到StudentData表并将某些属性映射到StudentEnrollmentInfo表,您可以看到Student实体分为以下两个表。

  • StudentData -包含学生FirstMidName和姓。

  • StudentEnrollmentInfo-包含EnrollmentDate。

生成数据库后,您将在数据库中看到下表,如下图所示。

实体分割

属性映射

Property方法用于为属于实体或复杂类型的每个属性配置属性。 Property方法用于获取给定属性的配置对象。您还可以使用Fluent API映射和配置域类的属性。

配置主键

主键的默认约定为-

  • 类定义名称为“ ID”或“ Id”的属性
  • 类名后跟“ ID”或“ Id”

如果您的班级未遵循主键的默认约定,如学生班级的以下代码所示,则-

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 Enrollments { get; set; }
}

然后要将属性显式设置为主键,可以使用HasKey方法,如以下代码所示-

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

配置列

在Entity Framework中,默认情况下,Code First将为具有相同名称,顺序和数据类型的属性创建一列。但是,您也可以覆盖此约定,如以下代码所示。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

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

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

配置MaxLength属性

在以下示例中,“课程标题”属性不得超过24个字符。当用户指定的值长于24个字符,则用户将获得DbEntityValidationException异常。

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

配置Null或NotNull属性

在下面的示例中,“课程标题”属性是必需的,因此使用IsRequired方法创建NotNull列。同样,Student EnrollmentDate是可选的,因此我们将使用IsOptional方法在此列中允许空值,如以下代码所示。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

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

配置关系

在数据库的上下文中,关系是当一个表具有引用另一个表的主键的外键时,在两个关系数据库表之间存在的情况。使用Code First时,您可以通过定义域CLR类来定义模型。默认情况下,实体框架使用Code First约定将您的类映射到数据库架构。

  • 如果您使用Code First命名约定,则在大多数情况下,您可以依靠Code First根据外键和导航属性在表之间建立关系。

  • 如果它们不符合这些约定,那么您还可以使用一些配置来影响类之间的关系以及在Code First中添加配置时如何在数据库中实现这些关系。

  • 其中一些可在数据注释中找到,您可以使用Fluent API来应用一些甚至更复杂的注释。

配置一对一关系

在模型中定义一对一关系时,将在每个类中使用参考导航属性。在数据库中,两个表在关系的任一侧只能有一个记录。每个主键值只与相关表中的一个记录(或没有记录)相关。

  • 如果两个相关列都是主键或具有唯一约束,则将创建一对一关系。

  • 在一对一关系中,主键还充当外键,并且对于任何一个表都没有单独的外键列。

  • 这种类型的关系并不常见,因为大多数以这种方式相关的信息都在一个表中。

让我们看下面的示例,在该示例中,我们将另一个类添加到模型中以创建一对一关系。

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

如您在上面的代码中看到的那样,Key和ForeignKey属性用于StudentLogIn类中的ID属性,以便将其标记为主键和外键。

要使用Fluent API配置Student和StudentLogIn之间的一对零关系或一种关系,您需要重写OnModelCreating方法,如以下代码所示。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

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

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

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

在大多数情况下,实体框架可以推断出哪种类型是从属类型以及哪种是关系中的主体。但是,当关系的两端都是必需的,或者双方都是可选的时,实体框架将无法识别受抚养人和委托人。当关系的两端都需要时,可以使用HasRequired,如以下代码所示。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

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

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

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

生成数据库后,您将看到该关系已创建,如下图所示。

建立的关系

配置一对多关系

主键表仅包含一条记录,该记录与相关表中的任何记录都不相关。这是最常用的关系类型。

  • 在这种类型的关系中,表A中的一行可以在表B中具有许多匹配行,但是表B中的一行只能在表A中具有一个匹配行。

  • 外键在表上定义,代表关系的许多末端。

  • 例如,在上图中,“学生”和“入学”表之间存在一托关系,每个学生可能有许多入学,但每个入学仅属于一个学生。

下面是具有一对多关系的“学生和注册”,但是“注册”表中的外键没有遵循默认的“代码优先”约定。

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 Enrollments { get; set; }
}

在这种情况下,要使用Fluent API配置一对多关系,您需要使用HasForeignKey方法,如以下代码所示。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

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

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

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

生成数据库后,您将看到已创建关系,如下图所示。

HasRequired方法

在上面的示例中,HasRequired方法指定Student导航属性必须为Null。因此,每次添加或更新注册时,都必须为学生分配注册实体。为了解决这个问题,我们需要使用HasOptional方法而不是HasRequired方法。

配置多对多关系

两个表中的每个记录都可以与另一个表中的任何数量的记录(或没有记录)相关。

  • 您可以通过定义第三个表(称为联结表)来创建这种关系,该表的主键由表A和表B的外键组成。

  • 例如,学生表和课程表具有多对多关系。

以下是学生和课程类,其中学生和课程具有多方面的关系,因为这两个类都具有集合的导航属性“学生和课程”。换句话说,一个实体具有另一个实体集合。

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 Courses { get; set; }
   public virtual ICollection Enrollments { get; set; }
}

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

要配置学生和课程之间的多对多关系,可以使用Fluent API,如以下代码所示。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

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

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

生成数据库时,默认的Code First约定用于创建联接表。结果,将使用Course_CourseID和Student_ID列创建StudentCourses表,如下图所示。

联接表

如果要指定联接表名称和表中列的名称,则需要使用Map方法进行其他配置。

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

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

   // Configure many-to-many relationship 
   modelBuilder.Entity()

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

您可以看到在生成数据库时,表和列的名称已按照上述代码中的指定进行创建。

联接表

我们建议您逐步执行上述示例,以更好地理解。