Jakeuj's Notes master Help

關聯性

筆記一下尋覽屬性

結論

  1. 集合使用 ICollection<T> ,單一導覽屬性使用 T

  2. 如果關聯表對應主表的欄位不是主鍵PK,則需要使用 HasPrincipalKey 來指定主實體中用於關聯的屬性。

  3. 尋覽屬性一般不需要加 virtual 關鍵字,可以等到需要消極載入時,於安裝設定階段再調整。

  4. 集合尋覽屬性建議始終初始化為空集合,這樣可以避免 null 引發的例外。

  5. 集合尋覽屬性建議不加 ? 來標記為可為 null,而是直接初始化為空集合。(關聯性包含:一對多,一對零到多)

集合

參考官方文件,導覽屬性為集合的範例如下:

public class Blog { public int Id { get; set; } public string Name { get; set; } public virtual Uri SiteUri { get; set; } public ICollection<Post> Posts { get; } }

其中 Posts 的型別使用 ICollection<T> ,這樣的設計可以讓 EF Core 了解這是一個集合,並且可以進行適當的操作。

在 Entity Framework(EF)中,用於定義導覽屬性集合時,通常推薦使用 ICollection<T> 而不是 List<T> 或其他具體集合類型。下面來解釋原因以及它們之間的差異。

使用 ICollection<T> 的理由

  1. 抽象性與靈活性: ICollection<T> 是一個更抽象的接口,而 List<T> 是具體的實作。當你使用 ICollection<T> 作為集合屬性時,這樣的設計提供了更多靈活性,因為可以在未來輕鬆更改具體的實作類型(例如,改為 HashSet<T> 或其他集合)。 這樣的抽象性有助於程式碼的可維護性,避免依賴某種具體的集合實作,減少耦合。

  2. EF 自動設定: Entity Framework 在資料庫中載入實體時,會自動初始化導覽屬性。使用 ICollection<T> 可以允許 EF 使用它認為最合適的集合類型來進行初始化,這樣也方便 EF 更好地進行內部優化。 EF 通常會使用 HashSet<T> 來初始化 ICollection<T> ,這樣的初始化效率通常比 List<T> 更高。

  3. 資料操作: ICollection<T> 提供了增、刪等方法(例如 Add、Remove 等),這些對於集合的操作在很多場景下是足夠的。而 List<T> 則包含了更多的操作(如 Insert、IndexOf 等),這些方法在大多數情況下並不需要對導覽屬性進行操作,因此使用 ICollection<T> 更能精簡操作並避免一些不必要的錯誤。

  4. 一致性和最佳實踐: 使用 ICollection<T> 作為集合屬性是 Entity Framework 的一個最佳實踐。這使你的程式碼看起來更符合標準,並與其他開發人員的使用習慣一致。 導覽屬性只是用來表示關聯,而不一定需要執行具體的操作;因此, ICollection<T> 已經足夠涵蓋大多數的場景需求。

關聯

參考官方文件,單一導覽屬性的範例如下:

protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<User>() .HasOne(u => u.UserProfile) .WithOne(p => p.User) // 外鍵在 UserProfile 中 .HasForeignKey<UserProfile>(p => p.Username) // 主鍵在 User 中(但不是 PK) .HasPrincipalKey<User>(u => u.Username); }

在這個範例中,User 和 UserProfile 之間的關聯是一對一的,而且我們使用 HasPrincipalKey 來指定 User 的 Username 屬性作為關聯的鍵。

基本概念

在實體關聯性中,我們經常用到的概念有兩個:

Principal Entity(主實體):就是關聯中所參考的實體,通常是“擁有”其他實體的那個(例如:Blog 和 Post 的關係中,Blog 是 Post 的主實體)。 Dependent Entity(依賴實體):依賴於主實體的那個實體,它的外鍵指向主實體(例如:Post 依賴於 Blog)。 在通常情況下,外鍵會預設指向主實體的主鍵(Primary Key),例如 Id。但在某些情況下,可能需要使用主實體中的其他屬性來作為關聯的鍵,這時候就需要用到 HasPrincipalKey。

HasPrincipalKey 的使用

HasPrincipalKey 用於指定“主實體”中用於關聯的屬性,這個屬性不是主鍵。通常在 Fluent API 中會看到這樣的用法。

舉個例子來說,假設我們有兩個實體:User 和 UserProfile,而我們想讓 UserProfile 使用 User 的 Username 屬性(而不是主鍵 UserId)來建立關聯,那麼我們就會用到 HasPrincipalKey。

virtual 關鍵字

官方範例中,尋覽屬性 public ICollection<Post> Posts { get; } 並沒有使用 virtual 關鍵字,主要是目前一般情境並不建議使用延遲載入(詳情請 GPT N+1 問題)。

反過來說就是等遇到真正需要使用 Lazy Loading 時,再來考慮是否要加上 virtual 關鍵字。

畢竟還需要安裝 nuget 包 Microsoft.EntityFrameworkCore.Proxies ,並且在 OnConfiguring 中加上 UseLazyLoadingProxies() ,最後再來加virtual也還來得及。

集合初始化

初始化集合屬性:如果集合屬性是公開的或可以由其他程式碼操作的,建議始終初始化為空集合,這樣可以避免 null 引發的例外,也減少額外的 null 檢查。

public ICollection<Post>? Posts { get; set; } = new List<Post>();

標記為 virtual 並支援延遲載入:如果你希望使用延遲載入,導航屬性應標記為 virtual,同時可以考慮讓 Entity Framework 自己管理初始化。但即使在這種情況下,初始化空集合也是一個穩妥的做法。

可為 null 的集合

在 C# 8 啟用了 可空參考類型(Nullable Reference Types, NRT) 之後,建議將集合尋覽屬性定義為不可為 null,這樣可以避免 null 引發的例外。

  • 使用 ICollection<PackageUsage>? (加上 ?):

當集合可能不被初始化(即允許 null)且需要進行 null 檢查時,可以使用可空參考類型。 需要確保在每次使用時進行 null 檢查。

  • 使用 ICollection<PackageUsage> (不加 ?)並初始化:

通常是更好的選擇。集合屬性總是被初始化為空集合(例如 new List<PackageUsage>() ),這樣可以保證集合始終是有效的,避免 null 引起的例外。 這樣可以避免多餘的 null 檢查,使代碼更簡潔和穩定。

總結

  • 一對一:

導覽屬性是否可空決定關聯是否必須存在。如果關聯是可選的,可以使用 ? 來表示屬性可為 null。

  • 一對多:

一對多中的集合導覽屬性通常不應為 null,建議總是初始化為空集合,這樣可以方便操作集合且避免 null 檢查。

  • 一對零到多:

一對零到多的集合導覽屬性也建議初始化為空集合。 “多”方的單個實體的導覽屬性可以設為可空,表示這個關聯可能不存在。

參照

關聯性的簡介

Last modified: 19 November 2024