Entity Framework - Fluent API

Fluent API是一种指定模型配置的高级方法,除了一些数据注释无法实现的更高级配置外,还包括数据注释可以执行的所有操作.数据注释和流畅的API可以一起使用,但Code First优先使用Fluent API>数据注释>默认约定.

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

  • Code First Fluent API最常通过覆盖派生的DbContext上的OnModelCreating方法来访问.

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

在本章中,我们将继续使用包含学生,课程和注册的简单示例具有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<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; }
   }

}

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

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

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

Sr.否.方法名称&描述
1

ComplexType<TComplexType>

在模型中将类型注册为复杂类型,并返回可用于配置复杂类型的对象.对于相同类型,可以多次调用此方法以执行多行配置.

2

Entity<TEntityType>

将实体类型注册为模型的一部分,并返回一个可以用于配置实体.可以多次调用此方法,以便同一实体执行多行配置.

3

HasKey<TKey>

配置此实体类型的主键属性.

4

HasMany<TTargetEntity>

从此实体类型配置许多关系.

5

HasOptional<TTargetEntity>

从此实体类型配置可选关系.实体类型的实例将能够保存到数据库,而不指定此关系.数据库中的外键可以为空.

6

HasRequired<TTargetEntity>

从此实体类型配置所需的关系.除非指定了此关系,否则无法将实体类型的实例保存到数据库.数据库中的外键将是不可为空的.

7

Ignore<TProperty>

从模型中排除属性,使其不会映射到数据库. (继承自StructuralTypeConfiguration< TStructuralType>)

8

Property<T>

配置在此类型上定义的struct属性. (继承自StructuralTypeConfiguration< TStructuralType>)

9

ToTable(String)

配置此实体类型映射到的表名.

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<Course> Courses { get; set; }
   public virtual DbSet<Enrollment> Enrollments { get; set; }
   public virtual DbSet<Student> Students { get; set; }
}

将实体映射到表

使用默认约定,Code First将创建数据库表在上下文类中使用DbSet属性的名称,例如Courses,Enrollments和Students.但是如果你想要不同的表名,那么你可以覆盖这个约定,并且可以提供与DbSet属性不同的表名,如下面的代码所示.

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

生成数据库时,您将看到OnModelCreating方法中指定的表名.

OnModel Method

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

Entity Splitting允许您将来自多个表的数据组合到一个类中,并且它只能用于它们之间具有一对一关系的表.让我们看一下以下示例,其中Student信息被映射到两个表中.

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

在上面的代码中,通过将一些属性映射到StudentData表,可以看到Student实体被拆分为以下两个表.使用Map方法将属性传递给StudentEnrollmentInfo表.

  • StudentData : 包含学生FirstMidName和姓氏.

  • StudentEnrollmentInfo : 包含EnrollmentDate.

生成数据库时,您会在数据库中看到以下表格,如下图所示.

Entity Splitting

属性映射

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

配置主键

主键的默认约定是&减去;

  • 类定义名称为"ID"或"Id"的属性

  • 类name后跟"ID"或"Id"

如果您的类不遵循主键的默认约定,如以下Student代码所示class :

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

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

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

配置列

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

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

配置MaxLength属性

在以下示例中,课程标题属性不再是超过24个字符.当用户指定超过24个字符的值时,用户将获得DbEntityValidationException异常.

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");
   modelBuilder.Entity<Course>().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<Course>().Property(p ⇒ p.Title).IsRequired();
   modelBuilder.Entity<Student>().Property(p ⇒ p.EnrollmentDate).IsOptional();
	
   //modelBuilder.Entity<Student>().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<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; }
}

正如您在上面的代码中看到的那样,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<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
}

在大多数情况下,实体框架可以推断哪个类型是从属关系,哪个是关系中的主体.但是,当关系的两端都是必需的或双方都是可选的时,实体框架无法识别依赖关系和主体.当需要关系的两端时,您可以使用HasRequired,如下面的代码所示.

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

生成数据库时,您将看到创建了如下图所示的关系.

创建关系

配置一对多关系

主键表只包含一个与相关表中的无,一个或多个记录相关的记录.这是最常用的关系类型.

  • 在这种关系中,表A中的一行可以有很多种匹配表B中的行,但表B中的一行只能在表A中有一个匹配的行.

  • 外键是在表示的表上定义的关系的很多结束.

  • 例如,在上图中,学生和报名表有一对一的关系,每个学生可能有很多报名,但是每个注册只属于一个学生.

以下是具有一对多关系的学生和注册,但是外键在注册表中没有遵循默认的Code First约定.

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

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

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

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

HasRequired Method

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

配置多对多关系

两个表中的每条记录都可以关联在另一个表中任意数量的记录(或没有记录).

  • 您可以通过定义一个第三个表,称为联结表,其主键由表A和表B中的外键组成.

  • 例如,Student表和课程表有多对多的关系.

以下是学生和课程有很多关系的学生和课程课程,因为这两个类都有导航属性学生和课程是集合.换句话说,一个实体有另一个实体集合.

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

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

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

默认的Code First约定用于在生成数据库时创建连接表.因此,使用Course_CourseID和Student_ID列创建StudentCourses表,如下图所示.

Join表

如果要指定表格中的连接表名称和列名称,则需要使用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");
   }); 
}

您可以看到生成数据库的时间,表格和列名称是按照上面的代码中的指定创建的.

Join Table

我们建议您逐步执行上述示例为了更好地理解而采取的步骤方式.