關聯性
筆記一下尋覽屬性
結論
集合使用
ICollection<T>
,單一導覽屬性使用T
。如果關聯表對應主表的欄位不是主鍵PK,則需要使用
HasPrincipalKey
來指定主實體中用於關聯的屬性。尋覽屬性一般不需要加 virtual 關鍵字,可以等到需要消極載入時,於安裝設定階段再調整。
集合尋覽屬性建議始終初始化為空集合,這樣可以避免 null 引發的例外。
集合尋覽屬性建議不加
?
來標記為可為 null,而是直接初始化為空集合。(關聯性包含:一對多,一對零到多)
集合
參考官方文件,導覽屬性為集合的範例如下:
其中 Posts 的型別使用 ICollection<T>
,這樣的設計可以讓 EF Core 了解這是一個集合,並且可以進行適當的操作。
在 Entity Framework(EF)中,用於定義導覽屬性集合時,通常推薦使用 ICollection<T>
而不是 List<T>
或其他具體集合類型。下面來解釋原因以及它們之間的差異。
使用 ICollection<T>
的理由
抽象性與靈活性:
ICollection<T>
是一個更抽象的接口,而List<T>
是具體的實作。當你使用ICollection<T>
作為集合屬性時,這樣的設計提供了更多靈活性,因為可以在未來輕鬆更改具體的實作類型(例如,改為HashSet<T>
或其他集合)。 這樣的抽象性有助於程式碼的可維護性,避免依賴某種具體的集合實作,減少耦合。EF 自動設定: Entity Framework 在資料庫中載入實體時,會自動初始化導覽屬性。使用
ICollection<T>
可以允許 EF 使用它認為最合適的集合類型來進行初始化,這樣也方便 EF 更好地進行內部優化。 EF 通常會使用HashSet<T>
來初始化ICollection<T>
,這樣的初始化效率通常比List<T>
更高。資料操作:
ICollection<T>
提供了增、刪等方法(例如 Add、Remove 等),這些對於集合的操作在很多場景下是足夠的。而List<T>
則包含了更多的操作(如 Insert、IndexOf 等),這些方法在大多數情況下並不需要對導覽屬性進行操作,因此使用ICollection<T>
更能精簡操作並避免一些不必要的錯誤。一致性和最佳實踐: 使用
ICollection<T>
作為集合屬性是 Entity Framework 的一個最佳實踐。這使你的程式碼看起來更符合標準,並與其他開發人員的使用習慣一致。 導覽屬性只是用來表示關聯,而不一定需要執行具體的操作;因此,ICollection<T>
已經足夠涵蓋大多數的場景需求。
關聯
參考官方文件,單一導覽屬性的範例如下:
在這個範例中,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 檢查。
標記為 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 檢查。
一對零到多:
一對零到多的集合導覽屬性也建議初始化為空集合。 “多”方的單個實體的導覽屬性可以設為可空,表示這個關聯可能不存在。