Fluent API - 設定和對應屬性和類型
使用 Entity Framework Code First 時,預設行為是使用一組內嵌到 EF 的慣例,將您的 POCO 類別對應至資料表。 不過,有時候,您無法或不想遵循這些慣例,而且需要將實體對應至慣例所指定以外的專案。
有兩種主要方式可讓您設定 EF 使用慣例以外的專案,也就是 注釋 或 EFs Fluent API。 批註只涵蓋 Fluent API 功能的子集,因此無法使用注釋達成對應案例。 本文旨在示範如何使用 Fluent API 來設定屬性。
程式碼第一個 Fluent API 最常透過覆寫 衍生 DbCoNtext 上的 OnModelCreating 方法來存取。 下列範例旨在示範如何使用 Fluent API 執行各種工作,並允許您將程式碼複製出來,並自訂它以符合您的模型,如果您想要查看模型可以依現成使用,則會在本文結尾提供它。
全模型設定
預設架構 (EF6 及更新版本)
從 EF6 開始,您可以使用 DbModelBuilder 上的 HasDefaultSchema 方法,指定要用於所有資料表、預存程式等的資料庫架構。對於您明確設定不同架構的任何物件,將會覆寫此預設設定。
modelBuilder.HasDefaultSchema("sales");
自訂慣例 (EF6 及更新版本)
從 EF6 開始,您可以建立自己的慣例來補充 Code First 中包含的慣例。 如需詳細資訊,請參閱 自訂程式碼第一慣例 。
屬性對應
Property 方法可用來設定屬於實體或複雜類型之每個屬性的屬性。 Property 方法可用來取得指定屬性的組態物件。 組態物件上的選項專屬於所設定的類型;IsUnicode 僅適用于字串屬性,例如。
設定主鍵
主鍵的 Entity Framework 慣例為:
- 您的類別會定義名稱為 「ID」 或 「Id」 的屬性
- 或類別名稱後面接著 「ID」 或 「Id」
若要將屬性明確設定為主鍵,您可以使用 HasKey 方法。 在下列範例中,HasKey 方法可用來在 OfficeAssignment 類型上設定 InstructorID 主鍵。
modelBuilder.Entity<OfficeAssignment>().HasKey(t => t.InstructorID);
設定複合主鍵
下列範例會將 DepartmentID 和 Name 屬性設定為 Department 類型的複合主鍵。
modelBuilder.Entity<Department>().HasKey(t => new { t.DepartmentID, t.Name });
關閉數值主鍵的識別
下列範例會將 DepartmentID 屬性設定為 System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None,表示資料庫不會產生該值。
modelBuilder.Entity<Department>().Property(t => t.DepartmentID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
指定屬性的長度上限
在下列範例中,Name 屬性應該不超過 50 個字元。 如果您使值超過 50 個字元,您將會收到 DbEntityValidationException 例外狀況 。 如果 Code First 從此模型建立資料庫,它也會將 Name 資料行的最大長度設定為 50 個字元。
modelBuilder.Entity<Department>().Property(t => t.Name).HasMaxLength(50);
將屬性設定為必要
在下列範例中,需要 Name 屬性。 如果您未指定 Name,您將會收到 DbEntityValidationException 例外狀況。 如果 Code First 從此模型建立資料庫,則用來儲存此屬性的資料行通常不可為 Null。
注意
在某些情況下,即使需要 屬性,資料庫中的資料行可能無法為 Null。 例如,針對多個類型使用 TPH 繼承策略資料時,會儲存在單一資料表中。 如果衍生類型包含必要的屬性,則無法將資料行設為不可為 Null,因為階層中的所有類型都不會有這個屬性。
modelBuilder.Entity<Department>().Property(t => t.Name).IsRequired();
在一或多個屬性上設定索引
注意
僅限 EF6.1 及更新版本 - Index 屬性是在 Entity Framework 6.1 中引進的。 如果您使用舊版,本節中的資訊不適用。
Fluent API 原生不支援建立索引,但您可以透過 Fluent API 使用 IndexAttribute 的支援 。 索引屬性會藉由在模型上加入模型批註來處理,然後稍後在管線中轉換成資料庫中的 Index。 您可以使用 Fluent API 手動新增這些相同的注釋。
若要這樣做,最簡單的方式是建立 IndexAttribute 的 實例,其中包含新索引的所有設定。 然後,您可以建立 IndexAnnotation 的 實例,這是 EF 特定類型,可將 IndexAttribute 設定轉換成 可儲存在 EF 模型上的模型批註。 接著,這些可以傳遞至 Fluent API 上的 HasColumnAnnotation 方法,並指定注釋的名稱 Index 。
modelBuilder
.Entity<Department>()
.Property(t => t.Name)
.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute()));
如需 IndexAttribute 中 可用設定的完整清單,請參閱 Code First 資料批註 的 Index 一 節。 這包括自訂索引名稱、建立唯一索引,以及建立多資料行索引。
您可以將 IndexAttribute 陣列 傳遞至 IndexAnnotation 的 建構函式,在單一屬性上指定多個索引批註。
modelBuilder
.Entity<Department>()
.Property(t => t.Name)
.HasColumnAnnotation(
"Index",
new IndexAnnotation(new[]
{
new IndexAttribute("Index1"),
new IndexAttribute("Index2") { IsUnique = true }
})));
指定不要將 CLR 屬性對應至資料庫中的資料行
下列範例示範如何指定 CLR 類型的屬性未對應至資料庫中的資料行。
modelBuilder.Entity<Department>().Ignore(t => t.Budget);
將 CLR 屬性對應至資料庫中的特定資料行
下列範例會將 Name CLR 屬性對應至 DepartmentName 資料庫資料行。
modelBuilder.Entity<Department>()
.Property(t => t.Name)
.HasColumnName("DepartmentName");
重新命名模型中未定義的外鍵
如果您選擇不要在 CLR 類型上定義外鍵,但想要指定它在資料庫中應該擁有的名稱,請執行下列動作:
modelBuilder.Entity<Course>()
.HasRequired(c => c.Department)
.WithMany(t => t.Courses)
.Map(m => m.MapKey("ChangedDepartmentID"));
設定 String 屬性是否支援 Unicode 內容
根據預設,字串為 Unicode (SQL Server 中的 Nvarchar)。 您可以使用 IsUnicode 方法來指定字串應為 Varchar 類型。
modelBuilder.Entity<Department>()
.Property(t => t.Name)
.IsUnicode(false);
設定資料庫資料行的資料類型
HasColumnType 方法可讓您對應至相同基本類型的不同標記法。 使用此方法不會讓您在執行時間執行任何資料轉換。 請注意,IsUnicode 是將資料行設定為 Varchar 的慣用方式,因為它與資料庫無關。
modelBuilder.Entity<Department>()
.Property(p => p.Name)
.HasColumnType("varchar");
在複雜類型上設定屬性
有兩種方式可以在複雜型別上設定純量屬性。
您可以在 ComplexTypeConfiguration 上呼叫 Property。
modelBuilder.ComplexType<Details>()
.Property(t => t.Location)
.HasMaxLength(20);
您也可以使用點標記法來存取複雜類型的屬性。
modelBuilder.Entity<OnsiteCourse>()
.Property(t => t.Details.Location)
.HasMaxLength(20);
將屬性設定為做為開放式並行權杖
若要指定實體中的屬性代表並行權杖,您可以使用 ConcurrencyCheck 屬性或 IsConcurrencyToken 方法。
modelBuilder.Entity<OfficeAssignment>()
.Property(t => t.Timestamp)
.IsConcurrencyToken();
您也可以使用 IsRowVersion 方法,將 屬性設定為資料庫中的資料列版本。 將 屬性設定為數據列版本會自動將它設定為開放式並行存取權杖。
modelBuilder.Entity<OfficeAssignment>()
.Property(t => t.Timestamp)
.IsRowVersion();
類型對應
指定類別為複雜類型
依照慣例,沒有指定主鍵的類型會被視為複雜型別。 在某些情況下,Code First 不會偵測到複雜類型(例如,如果您有稱為識別碼的屬性,但您並不表示它是主鍵)。 在這種情況下,您會使用 Fluent API 明確指定類型為複雜類型。
modelBuilder.ComplexType<Details>();
指定不要將 CLR 實體類型對應至資料庫中的資料表
下列範例示範如何排除 CLR 類型,使其無法對應至資料庫中的資料表。
modelBuilder.Ignore<OnlineCourse>();
將實體類型對應至資料庫中的特定資料表
Department 的所有屬性都會對應至名為 t_ Department 之資料表中的資料行。
modelBuilder.Entity<Department>()
.ToTable("t_Department");
您也可以指定如下的架構名稱:
modelBuilder.Entity<Department>()
.ToTable("t_Department", "school");
對應每個階層的資料表 (TPH) 繼承
在 TPH 對應案例中,繼承階層中的所有類型都會對應至單一資料表。 使用歧視性資料行來識別每個資料列的類型。 使用 Code First 建立模型時,TPH 是參與繼承階層之類型的預設策略。 根據預設,將歧視性資料行新增至名稱為 「Discriminator」 的資料表,而階層中每個型別的 CLR 類型名稱則用於歧視性值。 您可以使用 Fluent API 來修改預設行為。
modelBuilder.Entity<Course>()
.Map<Course>(m => m.Requires("Type").HasValue("Course"))
.Map<OnsiteCourse>(m => m.Requires("Type").HasValue("OnsiteCourse"));
對應資料表個別類型 (TPT) 繼承
在 TPT 對應案例中,所有類型都會對應至個別資料表。 單獨屬於基底型別 (Base Type) 或衍生型別 (Derived Type) 的屬性會儲存在對應至該型別的資料表中。 對應至衍生型別的資料表也會儲存外鍵,以聯結衍生資料表與基表。
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<OnsiteCourse>().ToTable("OnsiteCourse");
對應每個具體資料表類別 (TPC) 繼承
在 TPC 對應案例中,階層中的所有非抽象類別型都會對應至個別資料表。 對應至衍生類別的資料表與對應至資料庫中基類的資料表沒有任何關聯性。 類別的所有屬性,包括繼承的屬性,都會對應至對應資料表的資料行。
呼叫 MapInheritedProperties 方法來設定每個衍生類型。 MapInheritedProperties 會將繼承自基類的所有屬性重新對應至衍生類別資料表中的新資料行。
注意
請注意,由於參與 TPC 繼承階層的資料表不會共用主鍵,因此當您在資料表中插入對應至子類別的資料表時,如果資料庫產生的值具有相同的識別種子,則實體索引鍵會重複。 若要解決此問題,您可以為每個資料表指定不同的初始種子值,或關閉主鍵屬性上的身分識別。 使用 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");
});
將實體類型的屬性對應至資料庫中的多個資料表 (實體分割)
實體分割可讓實體類型的屬性分散到多個資料表。 在下列範例中,Department 實體分成兩個數據表:Department 和 DepartmentDetails。 實體分割會使用 Map 方法的多個呼叫,將屬性子集對應至特定資料表。
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");
});
將多個實體類型對應至資料庫中的一個資料表(資料表分割)
下列範例會將兩個共用主鍵的實體類型對應至一個資料表。
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");
將實體類型對應至插入/更新/刪除預存程式 (EF6 及更新版本)
從 EF6 開始,您可以對應實體以使用預存程式來插入更新和刪除。 如需詳細資訊, 請參閱 Code First Insert/Update/Delete 預存程式 。
範例中使用的模型
下列 Code First 模型會用於此頁面上的範例。
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; }
}