關聯性、導覽屬性和外鍵

本文概述 Entity Framework 如何管理實體之間的關聯性。 它也提供如何對應及操作關聯性的一些指引。

EF 中的關聯性

在關係資料庫中,資料表之間的關聯性(也稱為關聯)是透過外鍵來定義。 外鍵 (FK) 是資料行或資料行的組合,用來建立及強制執行兩個數據表中的資料之間的連結。 一般有三種類型的關聯性:一對一、一對多和多對多。 在一對多關聯性中,外鍵定義于代表關聯性之多端的資料表上。 多對多關聯性牽涉到定義第三個數據表(稱為連接點或聯結資料表),其主鍵是由這兩個相關資料表的外鍵所組成。 在一對一關聯性中,主鍵會額外作為外鍵,而且任一資料表沒有個別的外鍵資料行。

下圖顯示兩個參與一對多關聯性的資料表。 Course 資料表是相依資料表,因為它包含 連結至 Department 資料表的 DepartmentID 資料行。

Department and Course tables

在 Entity Framework 中,實體可以透過關聯或關聯性與其他實體相關。 每個關聯性都包含兩個端點,描述該關聯性中兩個實體的實體類型和型別的乘數(一、零或一或多個)。 關聯性可能受引用條件約束所控管,該條件約束描述關聯性中的哪一個結尾是主體角色,而後者是相依角色。

導覽屬性提供在兩個實體類型之間巡覽關聯的方式。 每個物件都可以針對它所參與的每項關聯性擁有導覽屬性。 導覽屬性可讓您在雙向巡覽和管理關聯性,傳回參考物件(如果多重性是一或零或一個)或集合(如果多重性很多)。 您也可以選擇有單向導覽,在此情況下,您只會在參與關聯性的其中一個類型上定義導覽屬性,而不是在這兩種類型上定義。

建議在對應至資料庫中外鍵的模型中包含屬性。 包括了外部索引鍵之後,您便可以透過修改相依物件上的外部索引鍵值來建立或變更關聯性。 這個類型的關聯稱為外部索引鍵關聯。 使用中斷連線的實體時,使用外鍵更為重要。 請注意,使用 1 對 1 或 1 對 0 時。1 關聯性,沒有個別的外鍵資料行,主鍵屬性會做為外鍵,而且一律包含在模型中。

當模型中未包含外鍵資料行時,關聯資訊會以獨立物件的形式進行管理。 關聯性是透過物件參考來追蹤,而不是外鍵屬性。 這種類型的關聯稱為 獨立關聯 。 修改 獨立關聯 最常見的方法是修改針對參與關聯之每個實體所產生的導覽屬性。

您可以選擇在模型中使用一種或兩種關聯類型。 不過,如果您有純多對多關聯性,且聯結資料表只包含外鍵,EF 會使用獨立關聯來管理這類多對多關聯性。   

下圖顯示以 Entity Framework 設計工具建立的概念模型。 此模型包含兩個參與一對多關聯性的實體。 這兩個實體都有導覽屬性。 Course 是相依實體,且已 定義 DepartmentID 外鍵屬性。

Department and Course tables with navigation properties

下列程式碼片段顯示使用 Code First 建立的相同模型。

public class Course
{
  public int CourseID { get; set; }
  public string Title { get; set; }
  public int Credits { get; set; }
  public int DepartmentID { get; set; }
  public virtual Department Department { get; set; }
}

public class Department
{
   public Department()
   {
     this.Courses = new HashSet<Course>();
   }  
   public int DepartmentID { get; set; }
   public string Name { get; set; }
   public decimal Budget { get; set; }
   public DateTime StartDate { get; set; }
   public int? Administrator {get ; set; }
   public virtual ICollection<Course> Courses { get; set; }
}

設定或對應關聯性

此頁面的其餘部分涵蓋如何使用關聯性來存取及運算元據。 如需在模型中設定關聯性的資訊,請參閱下列頁面。

建立和修改關聯性

在外鍵關聯 中,當您變更關聯性時,具有 EntityState.Unchanged 狀態的相依物件狀態會變更為 EntityState.Modified 。 在獨立的關聯性中,變更關聯性並不會更新相依物件的狀態。

下列範例示範如何使用外鍵屬性和導覽屬性來關聯相關物件。 使用外鍵關聯,您可以使用任一方法來變更、建立或修改關聯性。 使用獨立關聯,則無法使用外部索引鍵屬性。

  • 將新值指派給外鍵屬性,如下列範例所示。

    course.DepartmentID = newCourse.DepartmentID;
    
  • 下列程式碼會藉由將外鍵設定為 null 來移除關聯性。 請注意,外鍵屬性必須是可為 Null 的。

    course.DepartmentID = null;
    

    注意

    如果參考處於新增狀態(在此範例中為 course 物件),在呼叫 SaveChanges 之前,參考導覽屬性將不會與新物件的索引鍵值同步處理。 不會同步化是因為物件內容未包含新加入之物件的永久索引鍵,除非先儲存這些索引鍵。 如果您在設定關聯性時必須立即完全同步處理新的物件,請使用下列其中一種方法。*

  • 將新物件指派給導覽屬性。 下列程式碼會建立課程與 department 之間的關聯性。 如果物件附加至內容, course 也會將 加入集合 department.Courses 中,而 物件上的 course 對應外鍵屬性會設定為部門的索引鍵屬性值。

    course.Department = department;
    
  • 若要刪除關聯性,請將導覽屬性設定為 null 。 如果您使用以 .NET 4.0 為基礎的 Entity Framework,則必須先載入相關端,再將它設定為 Null。 例如:

    context.Entry(course).Reference(c => c.Department).Load();
    course.Department = null;
    

    從以 .NET 4.5 為基礎的 Entity Framework 5.0 開始,您可以將關聯性設定為 null,而不需要載入相關的結尾。 您也可以使用下列方法,將目前的值設定為 null。

    context.Entry(course).Reference(c => c.Department).CurrentValue = null;
    
  • 在實體集合中刪除或加入物件。 例如,您可以將 型別 Course 的物件加入至 department.Courses 集合。 此作業會建立特定 課程 與特定 department 之間的關聯性。 如果物件附加至內容,則 course 物件上的 部門參考和外鍵屬性將會設定為適當的 department

    department.Courses.Add(newCourse);
    
  • 使用 ChangeRelationshipState 方法來變更兩個實體物件之間指定關聯性的狀態。 使用 N 層應用程式和 獨立關聯 時,最常使用此方法(無法與外鍵關聯搭配使用)。 此外,若要使用此方法,您必須下拉至 ObjectContext ,如下列範例所示。
    在下列範例中,Instructors 與 Courses 之間有多對多關聯性。 ChangeRelationshipState呼叫 方法並傳遞 EntityState.Added 參數,讓 SchoolContext 知道兩個物件之間已新增關聯性:

    
    ((IObjectContextAdapter)context).ObjectContext.
      ObjectStateManager.
      ChangeRelationshipState(course, instructor, c => c.Instructor, EntityState.Added);
    

    請注意,如果您要更新關聯性(不只是新增)關聯性,您必須在新增關聯性之後刪除舊關聯性:

    ((IObjectContextAdapter)context).ObjectContext.
      ObjectStateManager.
      ChangeRelationshipState(course, oldInstructor, c => c.Instructor, EntityState.Deleted);
    

同步處理外鍵和導覽屬性之間的變更

當您使用上述其中一種方法來變更附加至內容的物件關聯性時,Entity Framework 必須保持外鍵、參考和集合同步。Entity Framework 會自動管理具有 Proxy 之 POCO 實體的這項同步處理(也稱為關聯性修正)。 如需詳細資訊,請參閱 使用 Proxy

如果您使用不含 Proxy 的 POCO 實體,您必須確定 已呼叫 DetectChanges 方法以同步處理內容中的相關物件。 請注意,下列 API 會自動觸發 DetectChanges 呼叫。

  • DbSet.Add
  • DbSet.AddRange
  • DbSet.Remove
  • DbSet.RemoveRange
  • DbSet.Find
  • DbSet.Local
  • DbContext.SaveChanges
  • DbSet.Attach
  • DbContext.GetValidationErrors
  • DbContext.Entry
  • DbChangeTracker.Entries
  • 針對 執行 LINQ 查詢 DbSet

在 Entity Framework 中,您通常會使用導覽屬性來載入與所定義關聯所傳回實體相關的實體。 如需詳細資訊,請參閱 載入相關物件

注意

在外部索引鍵關聯中,當您載入相依物件的相關端時,相關物件將根據目前在記憶體中之相依外部索引鍵值來載入:

    // Get the course where currently DepartmentID = 2.
    Course course = context.Courses.First(c => c.DepartmentID == 2);

    // Use DepartmentID foreign key property
    // to change the association.
    course.DepartmentID = 3;

    // Load the related Department where DepartmentID = 3
    context.Entry(course).Reference(c => c.Department).Load();

在獨立關聯中,會根據目前在資料庫中的外部索引鍵值來查詢相依物件的相關端。 不過,如果修改關聯性,而相依物件上的參考屬性會指向物件內容中載入的不同主體物件,Entity Framework 會嘗試在用戶端上定義關聯性時建立關聯性。

管理並行

在外鍵和獨立關聯中,並行檢查是以模型中定義的實體索引鍵和其他實體屬性為基礎。 使用 EF Designer 建立模型時,將 屬性設定為 固定 ConcurrencyMode 以指定應該檢查屬性是否為並行。 使用 Code First 來定義模型時,請在 ConcurrencyCheck 您想要檢查的並行屬性上使用注釋。 使用 Code First 時,您也可以使用 TimeStamp 批註來指定應該檢查屬性是否為並行。 在指定的類別中,您只能有一個 timestamp 屬性。 Code First 會將此屬性對應至資料庫中不可為 Null 的欄位。

建議您在處理參與並行檢查和解析的實體時,一律使用外鍵關聯。

如需詳細資訊,請參閱 處理並行衝突

使用重迭的索引鍵

重疊索引鍵是複合索引鍵,其索引鍵中的部分屬性也是實體中另一個索引鍵的一部分。 在獨立關聯中不能有重疊索引鍵。 若要變更包括重疊索引鍵的外部索引鍵關聯,我們建議您修改外部索引鍵值,而不是使用物件參考。