實體框架 - Fluent API



Fluent API 是一種指定模型配置的更高階方法,它涵蓋了資料註解可以執行的所有操作,此外還有一些資料註解無法實現的更高階配置。資料註解和 Fluent API 可以一起使用,但 Code First 優先順序為 Fluent API > 資料註解 > 預設約定。

  • Fluent API 是配置領域類(domain classes)的另一種方法。

  • Code First Fluent API 最常透過覆蓋派生 DbContext 上的 OnModelCreating 方法來訪問。

  • 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<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 約定。以下是一些方法。

序號 方法名稱及描述
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>

配置在此型別上定義的結構屬性。(繼承自 StructuralTypeConfiguration<TStructuralType>)

9

ToTable(String)

配置此實體型別對映到的表名。

Fluent API 允許您配置實體或其屬性,無論您是想更改它們對映到資料庫的方式還是它們彼此之間如何關聯。您可以使用配置影響各種對映和建模。以下是 Fluent API 支援的主要對映型別:

  • 實體對映
  • 屬性對映

實體對映

實體對映只是一些簡單的對映,這些對映會影響實體框架對類如何對映到資料庫的理解。所有這些我們在資料註解中都討論過,在這裡我們將看到如何使用 Fluent API 來實現相同的功能。

  • 因此,與其進入領域類來新增這些配置,我們可以在上下文中執行此操作。

  • 首先是重寫 OnModelCreating 方法,該方法提供 modelBuilder 進行操作。

預設架構

生成資料庫時,預設架構為 dbo。您可以使用 DbModelBuilder 上的 HasDefaultSchema 方法指定用於所有表、儲存過程等的資料庫架構。

讓我們來看下面的示例,其中應用了 admin 架構。

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

實體拆分(將實體對映到多個表)

實體拆分允許您將來自多個表的資料組合到單個類中,並且只能與表之間存在一對一關係的表一起使用。讓我們來看下面的示例,其中學生資訊對映到兩個表中。

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

在上面的程式碼中,您可以看到 Student 實體透過使用 Map 方法將一些屬性對映到 StudentData 表,將一些屬性對映到 StudentEnrollmentInfo 表來拆分為以下兩個表。

  • **StudentData** - 包含 Student FirstMidName 和 Last Name。

  • **StudentEnrollmentInfo** - 包含 EnrollmentDate。

生成資料庫時,您將在資料庫中看到以下表,如下圖所示。

Entity Splitting

屬性對映

Property 方法用於配置屬於實體或複雜型別的每個屬性的屬性。Property 方法用於獲取給定屬性的配置物件。您還可以使用 Fluent API 對映和配置領域類的屬性。

配置主鍵

主鍵的預設約定是:

  • 類定義一個名為“ID”或“Id”的屬性
  • 類名後跟“ID”或“Id”

如果您的類不遵循主鍵的預設約定,如下面的 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; }
}

然後,要顯式地將屬性設定為主鍵,可以使用 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 將為屬性建立具有相同名稱、順序和資料型別的列。但是,您也可以覆蓋此約定,如下面的程式碼所示。

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 屬性

在下面的示例中,Course Title 屬性的長度不應超過 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 屬性

在下面的示例中,Course Title 屬性是必需的,因此使用 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; }
}

正如您在上面的程式碼中看到的,為了將`StudentLogIn`類的`ID`屬性標記為主鍵和外部索引鍵,使用了`Key`和`ForeignKey`屬性。

要使用 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);  
}

生成資料庫時,您將看到關係的建立方式,如下面的影像所示。

Created Relationship

配置一對多關係

主鍵表只包含一條記錄,該記錄與相關表中的零條、一條或多條記錄相關。這是最常用的關係型別。

  • 在這種型別的關係中,表 A 中的一行可以有多行與表 B 中的行匹配,但表 B 中的一行只能與表 A 中的一行匹配。

  • 外部索引鍵定義在表示關係多端的表上。

  • 例如,在上圖中,`Student`和`Enrollment`表具有多對一關係,每個學生可能有多個註冊,但每個註冊只屬於一個學生。

以下是具有多對一關係的`Student`和`Enrollment`,但`Enrollment`表中的外部索引鍵不遵循預設的 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`。因此,每次新增或更新`Enrollment`時,都必須為`Enrollment`實體分配`Student`。為了處理這個問題,我們需要使用`HasOptional`方法而不是`HasRequired`方法。

配置多對多關係

兩個表中的每條記錄都可以與另一個表中的任意數量的記錄(或沒有記錄)相關。

  • 您可以透過定義一個第三個表(稱為連線表)來建立這種關係,該表的鍵由表 A 和表 B 的外部索引鍵組成。

  • 例如,`Student`表和`Course`表具有多對多關係。

以下是`Student`和`Course`類,其中`Student`和`Course`具有多對多關係,因為這兩個類都具有作為集合的導航屬性`Students`和`Courses`。換句話說,一個實體擁有另一個實體集合。

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 配置`Student`和`Course`之間的多對多關係,您可以使用以下程式碼所示的 Fluent API。

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 約定建立一個連線表。結果,將建立`StudentCourses`表,其中包含`Course_CourseID`和`Student_ID`列,如下面的影像所示。

Join Table

如果要指定連線表的名稱和表中列的名稱,則需要使用`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

我們建議您逐步執行以上示例,以便更好地理解。

廣告
© . All rights reserved.