上一篇文章我們講解了如何用 Fluent API 來配置/映射屬性和類型,本文將把重點放在其是如何配置關系的。
文中所使用代碼如下

public class Student { public int ID { get; set; } public string Name { get; set; } public DateTime EnrollmentDate { get; set; } // Navigation properties public virtual Address Address { get; set; } public virtual OtherInfo OtherInfo { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } } 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 int CourseID { get; set; } public string Title { get; set; } public int Credits { get; set; } // Foreign key public int DepartmentID { get; set; } public string DepartmentName { get; set; } public int SomeDepartmentID { get; set; } // Navigation properties public virtual Department Department { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } public virtual ICollection<Instructor> Instructors { get; set; } } public class Instructor { public int InstructorID { get; set; } public string Name { get; set; } public DateTime HireDate { get; set; } // Navigation properties public virtual ICollection<Course> Courses { get; set; } } public class Enrollment { public int EnrollmentID { get; set; } public int CourseID { get; set; } public int StudentID { get; set; } // Navigation property public virtual Course Course { get; set; } public virtual Student Student { get; set; } } public class Address { public int AddressId { get; set; } public string HomeAddress { get; set; } public string LiveAddress { get; set; } // Navigation property public virtual Student Student { get; set; } } public class OtherInfo { public int Id { get; set; } public string HomeAddress { get; set; } public string MailAddress { get; set; } public string PhoneNumber { get; set; } public string StudentID { get; set; } // Navigation property public virtual Student Student { get; set; } }
EntityTypeConfiguration<TEntityType>
上面是泛型類的部分方法截圖,一般我們通過 Code First Fluent API 來配置實體間的關系時都是從此泛型類的實例開始,此泛型類為我們提供了大部分的關系處理方法,如必須的 HasRequired ,可選的 HasOptional ,多個的 HasMany .
上面所有方法(除了 HasMany, HasOptional, HasRequired )的返回值都是泛型類 EntityTypeConfiguration<TEntityType> 的實例本身,這給鏈式操作提供了極大地方便.
HasRequired, HasOptional, HasMany 這三個方法的參數都是一個 lambda expression, 只不過前兩個用於表示實體關系(Relationship)間的導航屬性(navigation property),后一個則是導航屬性的集合(Collection)
HasRequired, HasOptional, HasMany 這三個方法的返回值都是可以繼續進行配置的泛型類實例,雖然名稱不同,方法名也略有不同,但方法主體所體現的思想是一致的
RequiredNavigationPropertyConfiguration<TEntityType, TTargetEntityType>
OptionalNavigationPropertyConfiguration<TEntityType, TTargetEntityType>
ManyNavigationPropertyConfiguration<TEntityType, TTargetEntityType>
三個泛型類都有類似於如下三個方法 WithRequired, WithOptional, WithMany, 這三個方法都提供了重載的無參版本,有參方法的參數也都是一個 lambda expression, 前兩個用於表示實體關系(Relationship)間的導航屬性(navigation property),后一個則是導航屬性的集合(Collection)
接下來,你可以使用方法 HasForeignKey 繼續配置外鍵屬性,方法的參數仍然是一個 lambda expression, 只不過代表一個屬性
DependentNavigationPropertyConfiguration<TDependentEntityType>
public CascadableNavigationPropertyConfiguration HasForeignKey<TKey>(Expression<Func<TDependentEntityType, TKey>> foreignKeyExpression);
其它兩個類主要包含方法 Map ,如下
ForeignKeyNavigationPropertyConfiguration
public CascadableNavigationPropertyConfiguration Map(Action<ForeignKeyAssociationMappingConfiguration> configurationAction);
ManyToManyNavigationPropertyConfiguration<TEntityType, TTargetEntityType>
public ManyToManyNavigationPropertyConfiguration<TEntityType, TTargetEntityType> Map(Action<ManyToManyAssociationMappingConfiguration> configurationAction); public ManyToManyNavigationPropertyConfiguration<TEntityType, TTargetEntityType> MapToStoredProcedures(); public ManyToManyNavigationPropertyConfiguration<TEntityType, TTargetEntityType> MapToStoredProcedures(Action<ManyToManyModificationStoredProceduresConfiguration<TEntityType, TTargetEntityType>> modificationStoredProcedureMappingConfigurationAction);
我們可以繼續在返回的實例上進行配置
CascadableNavigationPropertyConfiguration
public void WillCascadeOnDelete(); public void WillCascadeOnDelete(bool value);
我們看到了級聯刪除
Configuring Relationships
1:1,0 - Configuring a Required-to-Optional Relationship (One-to–Zero-or-One)
一個學生可以有一個或沒有其它信息(包括郵件地址、手機號等)
// Map one-to-zero or one relationship modelBuilder.Entity<OtherInfo>() .HasRequired(t => t.Student) .WithOptional(t => t.OtherInfo);
1:1 - Configuring a Relationship Where Both Ends Are Required (One-to-One)
一個學生肯定有一個地址信息(包含家庭住址、居住地址)
// Map one-to-one relationship modelBuilder.Entity<Address>() .HasRequired(t => t.Student) .WithRequiredPrincipal(t => t.Address);
1:N - Configuring a Required-to-Many Relationship (One-to-Many)
一個學生可以參加多門課程
// Map one-to-many relationship modelBuilder.Entity<Student>() .HasMany(t => t.Enrollments) .WithRequired(t => t.Student);
N:N - Configuring a Many-to-Many Relationship (Many-to-Many)
一個老師可以教授多門課程,一門課程也可以由多名老師教授
// Map one-to-many relationship modelBuilder.Entity<Course>() .HasMany(t => t.Instructors) .WithMany(t => t.Courses);
你還可以進一步指定中間連接表(數據庫將會創建中間連接表)
// Map one-to-many relationship modelBuilder.Entity<Course>() .HasMany(t => t.Instructors) .WithMany(t => t.Courses) .Map(m => { m.ToTable("CourseInstructor"); m.MapLeftKey("CourseID"); m.MapRightKey("InstructorID"); });
Configuring a Relationship with One Navigation Property - 基於導航屬性配置關系
如果關系是單向的,即兩個實體間只在一個實體上定義導航屬性。Code First Conventions 能夠自動推斷這種關系為 one-to-many .
例如你想在 Student 和 Address 兩個實體間建立 one-to-one 關系,而且只在 Address 實體上包含導航屬性,此時你就需要用 Code First Fluent API 配置這種關系
// Map one-to-one relationship modelBuilder.Entity<Address>() .HasRequired(t => t.Student) .WithRequiredPrincipal();
WillCascadeOnDelete - Enabling Cascade Delete (級聯刪除)
你可以使用 WillCascadeOnDelete 來級聯刪除關系,如果從屬主體上的外鍵是 not nullable, 那么 Code First 將設置級聯刪除,否則將不會設置級聯刪除,而只是僅僅把外鍵設置成 null
在 Code First Conventions 下是這樣移除級聯刪除的
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>()
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>()
Code First Fluent API 的方式如下
// Cascade Delete modelBuilder.Entity<Course>() .HasRequired(t => t.Department) .WithMany(t => t.Courses) .HasForeignKey(d => d.DepartmentID) .WillCascadeOnDelete(false);
Configuring a Composite Foreign Key - 配置組合外鍵
如果設置 Department 的主鍵為組合主鍵 DepartmentID, Name,則可以通過 Fluent API 為 Course 指定組合外鍵
// Composite primary key modelBuilder.Entity<Department>() .HasKey(d => new { d.DepartmentID, d.Name }); // Composite foreign key modelBuilder.Entity<Course>() .HasRequired(c => c.Department) .WithMany(d => d.Courses) .HasForeignKey(d => new { d.DepartmentID, d.DepartmentName });
Renaming a Foreign Key That Is Not Defined in the Model - 重命名外鍵
可以重命名外鍵名
// Renaming a Foreign Key That Is Not Defined in the Model modelBuilder.Entity<Course>() .HasRequired(c => c.Department) .WithMany(t => t.Courses) .Map(m => m.MapKey("ChangedDepartmentID"));
Configuring a Foreign Key Name That Does Not Follow the Code First Convention
如果從屬實體上的外鍵屬性名不符合 Code First Conventions 的規范,意即從屬實體上沒有外鍵,你可以通過如下方式來指定
// Configuring a Foreign Key Name That Does Not Follow the Code First Convention modelBuilder.Entity<Course>() .HasRequired(c => c.Department) .WithMany(d => d.Courses) .HasForeignKey(c => c.SomeDepartmentID);