Share via


關聯性導覽

EF Core 關聯性是由 外鍵所定義。 流覽會分層在外鍵上,以提供自然、面向對象檢視來讀取和操作關聯性。 藉由使用導覽,應用程式可以處理實體的圖形,而不需擔心外鍵值發生的情況。

重要

多個關聯性無法共享導覽。 任何外鍵最多都可以與一個從主體流覽至相依的導覽相關聯,而且最多只能與一個從相依至主體的瀏覽產生關聯。

提示

除非流覽是由延遲載入變更追蹤 Proxy 使用,否則不需要讓導覽變成虛擬。

參考導覽

導覽有兩種表單--參考和集合。 參考導覽是另一個實體的簡單對象參考。 它們代表一對多一對一關係中的「一」端。 例如:

public Blog TheBlog { get; set; }

參考導覽必須有 setter,但不需要是公用的。 參考導覽不應自動初始化為非 Null 預設值;這樣做相當於判斷實體不存在時存在。

使用 C# 可為 Null 的參考型別時,選擇性關聯性必須可為 Null 的參考導覽:

public Blog? TheBlog { get; set; }

必要關聯性的參考導覽可以是可為 Null 或不可為 Null。

集合導覽

集合導覽是 .NET 集合類型的實例;也就是說,任何實作 ICollection<T>的類型。 集合包含相關實體類型的實例,其中可以有任意數目。 它們代表了一對多多對多關係的“多”端。 例如:

public ICollection<Post> ThePosts { get; set; }

集合導覽不需要 setter。 初始化集合內嵌很常見,因此不需要檢查屬性是否為 null。 例如:

public ICollection<Post> ThePosts { get; } = new List<Post>();

提示

請勿不小心建立表示式主體屬性,例如 public ICollection<Post> ThePosts => new List<Post>();。 這會在每次存取屬性時建立新的空白集合實例,因此將無用做為導覽。

集合類型

基礎集合實例必須實 ICollection<T>作 ,而且必須有工作 Add 方法。 通常使用 List<T>HashSet<T>List<T> 對於少量的相關實體而言,效率很高,而且會維持穩定的順序。 HashSet<T> 對於大量實體,具有更有效率的查閱,但沒有穩定的排序。 您也可以使用自己的自訂集合實作。

重要

集合必須使用參考相等。 建立 HashSet<T> 集合導覽的 時,請務必使用 ReferenceEqualityComparer

陣列無法用於集合導覽,因為即使它們實 ICollection<T>作 ,方法 Add 也會在呼叫時擲回例外狀況。

即使集合實例必須是 ICollection<T>,集合也不需要公開為這類。 例如,通常會將瀏覽公開為 IEnumerable<T>,其提供應用程式程式代碼無法隨機修改的只讀檢視。 例如:

public class Blog
{
    public int Id { get; set; }
    public IEnumerable<Post> ThePosts { get; } = new List<Post>();
}

此模式的變化包括視需要操作集合的方法。 例如:

public class Blog
{
    private readonly List<Post> _posts = new();

    public int Id { get; set; }

    public IEnumerable<Post> Posts => _posts;

    public void AddPost(Post post) => _posts.Add(post);
}

應用程式程式代碼仍然可以將公開的集合 ICollection<T> 轉換成 ,然後加以操作。 如果這是一個考慮,則實體可能會傳回集合的防禦複本。 例如:

public class Blog
{
    private readonly List<Post> _posts = new();

    public int Id { get; set; }

    public IEnumerable<Post> Posts => _posts.ToList();

    public void AddPost(Post post) => _posts.Add(post);
}

請仔細考慮從這個 取得的值是否足夠高,超過每次存取導覽時建立集合複本的額外負荷。

提示

此最終模式的運作方式是,根據預設,EF 會透過其支援字段存取集合。 這表示 EF 本身會從實際集合新增和移除實體,而應用程式只會與集合的防禦性複本互動。

集合導覽的初始化

集合導覽可以立即由實體類型初始化:

public class Blog
{
    public ICollection<Post> Posts { get; } = new List<Post>();
}

或者懶散:

public class Blog
{
    private ICollection<Post>? _posts;

    public ICollection<Post> Posts => _posts ??= new List<Post>();
}

例如,如果EF需要將實體新增至集合導覽,則在執行查詢時,如果目前 null為 ,則會初始化集合。 建立的實例取決於流覽的公開類型。

  • 如果導覽公開為 HashSet<T>,則會建立using ReferenceEqualityComparerHashSet<T>實例。
  • 否則,如果導覽公開為具有無參數建構函式的具象類型,則會建立該具體類型的實例。 這也適用於 List<T>,但也適用於其他集合類型,包括自定義集合類型。
  • 否則,如果巡覽公開為 IEnumerable<T>、、 ICollection<T>ISet<T>,則會建立using ReferenceEqualityComparerHashSet<T>實例。
  • 否則,如果導覽公開為 IList<T>,則會建立的 List<T> 實例。
  • 否則,會擲回例外狀況。

注意

如果使用包含變更追蹤 Proxy 的通知實體,則會ObservableCollection<T>使用 和 ObservableHashSet<T> 來取代 List<T>HashSet<T>

重要

如變更追蹤檔中所述,EF 只會追蹤具有指定索引鍵值之任何實體的單一實例。 這表示做為導覽的集合必須使用 參考相等語意。 不會覆寫物件相等的實體類型預設會取得此專案。 請務必在建立 HashSet<T> 以做為導覽的 時使用 ReferenceEqualityComparer ,以確保它適用於所有實體類型。

設定導覽

在設定關聯性時,模型中會包含導覽。 也就是說,依照 慣例或在 HasOne模型建置 API 中使用 、 HasMany等。 大部分與導覽相關聯的組態都是藉由設定關聯性本身來完成。

不過,有些組態類型專屬於導覽屬性本身,而不是屬於整體關聯性組態的一部分。 這種類型的組態是使用 方法完成的 Navigation 。 例如,若要強制 EF 透過其 屬性存取導覽,而不是使用支援欄位:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Navigation(e => e.Posts)
        .UsePropertyAccessMode(PropertyAccessMode.Property);

    modelBuilder.Entity<Post>()
        .Navigation(e => e.Blog)
        .UsePropertyAccessMode(PropertyAccessMode.Property);
}

注意

呼叫 Navigation 無法用來建立導覽屬性。 它只會用來設定先前透過定義關聯性或慣例所建立的導覽屬性。

必要的導覽

如果需要關聯性,則需要從相依主體巡覽,這又表示外鍵屬性不可為 Null。 相反地,如果外鍵可為 Null,則瀏覽是選擇性的,因此關聯性是選擇性的。

從主體到相依的參考導覽不同。 在大部分情況下,主體實體一律可以存在,而不需要任何相依實體。 也就是說,必要的關聯性並不表示一律會有至少一個相依實體。 EF 模型沒有任何方法,也沒有任何標準方式在關係資料庫中,以確保主體與特定數目的相依性相關聯。 如果需要,則必須在應用程式(商業)邏輯中實作它。

此規則有一個例外狀況-當主體和相依類型共用關係資料庫中的相同數據表,或包含在檔中時。 這可能發生於擁有的類型,或共用相同數據表的非擁有類型。 在此情況下,從主體到相依的導覽屬性可以標示為必要,表示相依項目必須存在。

使用 Navigation 方法完成巡覽屬性的設定。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Navigation(e => e.BlogHeader)
        .IsRequired();
}