[英]EF codefirst : Should I initialize navigation properties?
我看過一些書(例如編程實體框架代碼首先 Julia Lerman )定義了他們的域類(POCO)而沒有初始化導航屬性,例如:
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public virtual ICollection<Address> Address { get; set; }
public virtual License License { get; set; }
}
其他一些書籍或工具(例如Entity Framework Power Tools )在生成 POCO 時會初始化類的導航屬性,例如:
public class User
{
public User()
{
this.Addresses = new IList<Address>();
this.License = new License();
}
public int Id { get; set; }
public string UserName { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
public virtual License License { get; set; }
}
編輯:
public class License
{
public License()
{
this.User = new User();
}
public int Id { get; set; }
public string Key { get; set; }
public DateTime Expirtion { get; set; }
public virtual User User { get; set; }
}
作為導航屬性的集合和引用之間存在明顯區別。 引用是一個實體。 集合包含實體。 這意味着初始化集合在業務邏輯方面毫無意義:它沒有定義實體之間的關聯。 設置參考確實如此。
因此,是否或如何初始化嵌入列表純粹是一個偏好問題。
至於“如何”,有些人更喜歡延遲初始化:
private ICollection<Address> _addresses;
public virtual ICollection<Address> Addresses
{
get { return this._addresses ?? (this._addresses = new HashSet<Address>());
}
它可以防止空引用異常,因此它有助於單元測試和操作集合,但它也可以防止不必要的初始化。 當一個類有相對較多的集合時,后者可能會有所不同。 缺點是它需要相對較多的管道,尤其是。 與沒有初始化的自動屬性相比。 此外,C# 中空傳播運算符的出現使得初始化集合屬性變得不那么緊迫。
...除非應用顯式加載
唯一的問題是初始化集合使得很難檢查實體框架是否加載了集合。 如果一個集合被初始化,像這樣的語句...
var users = context.Users.ToList();
...將創建具有空的、非空的Addresses
集合的User
對象(延遲加載放在一邊)。 檢查集合是否已加載需要像...
var user = users.First();
var isLoaded = context.Entry(user).Collection(c => c.Addresses).IsLoaded;
如果集合未初始化,則將執行簡單的null
檢查。 So when selective explicit loading is an important part of your coding practice, ie ...
if (/*check collection isn't loaded*/)
context.Entry(user).Collection(c => c.Addresses).Load();
...不初始化集合屬性可能更方便。
引用屬性是實體,因此為它們分配一個空對象是有意義的。
更糟糕的是,如果您在構造函數中啟動它們,EF 不會在具體化您的對象或通過延遲加載時覆蓋它們。 在您主動替換它們之前,它們將始終具有初始值。 更糟糕的是,您甚至可能最終將空實體保存在數據庫中!
還有另一個效果:不會發生關系修復。 關系修復是 EF 通過其導航屬性連接上下文中所有實體的過程。 當User
和Licence
分別加載時,仍會填充User.License
,反之亦然。 當然,除非在構造函數中初始化了License
。 對於 1:n 關聯也是如此。 如果Address
會在其構造函數中初始化User
, User.Addresses
不會填充User.Addresses
!
實體框架核心
實體框架核心(撰寫本文時為 2.1)中的關系修復不受構造函數中初始化的引用導航屬性的影響。 也就是說,當分別從數據庫中提取用戶和地址時,會填充導航屬性。
然而,延遲加載不會覆蓋初始化參考導航性能。
在 EF-core 3 中,初始化參考導航屬性會阻止Include
正常工作。
因此,總而言之,同樣在 EF-core 中,在構造函數中初始化引用導航屬性可能會導致麻煩。 不要這樣做。 反正也沒意義。
在我所有的項目中,我都遵循規則——“集合不應為空。它們要么是空的,要么有值。”
當創建這些實體是第三方代碼(例如 ORM)的責任並且您正在處理一個短期項目時,第一個示例是可能的。
第二個例子更好,因為
NullReferenceException
實踐領域驅動設計的人們將集合公開為只讀,並避免使用 setter。 (請參閱NHibernate 中只讀列表的最佳實踐是什么)
Q1:哪個更好? 為什么? 利弊?
最好公開非空集合,因為您可以避免在代碼中進行額外檢查(例如Addresses
)。 在您的代碼庫中擁有一份很好的合同。 但是我可以公開對單個實體的可空引用(例如License
)
Q2:在第二種方法中,如果License
類也有對User
類的引用,則會出現堆棧溢出。 這意味着我們應該有單向引用。(?)我們應該如何決定應該刪除哪一個導航屬性?
當我自己開發數據映射器模式時,我盡量避免雙向引用,並且很少有從子級到父級的引用。
當我使用 ORM 時,很容易有雙向引用。
當需要使用雙向參考集為我的單元測試構建測試實體時,我遵循以下步驟:
children collection
構建parent entity
。parent entity
每個child
添加到children collection
。 如果在License
類型中使用無參數構造函數,我將使user
屬性成為必需。
public class License
{
public License(User user)
{
this.User = user;
}
public int Id { get; set; }
public string Key { get; set; }
public DateTime Expirtion { get; set; }
public virtual User User { get; set; }
}
new
列表是多余的,因為您的 POCO 依賴於延遲加載。
延遲加載是在第一次訪問引用實體或實體的屬性時自動從數據庫加載實體或實體集合的過程。 使用 POCO 實體類型時,通過創建派生代理類型的實例,然后覆蓋虛擬屬性以添加加載鈎子來實現延遲加載。
如果您要刪除 virtual 修飾符,那么您將關閉延遲加載,在這種情況下,您的代碼將不再起作用(因為沒有任何東西可以初始化列表)。
請注意,延遲加載是實體框架支持的功能,如果您在 DbContext 的上下文之外創建類,那么依賴代碼顯然會遭受NullReferenceException
HTH
Q1:哪個更好? 為什么? 利弊?
在實體構造函數中設置虛擬屬性時的第二個變體有一個明確的問題,稱為“ 構造函數中的虛擬成員調用”。
至於第一個沒有初始化導航屬性的變體,有兩種情況取決於誰/什么創建了一個對象:
第一個變體在 Entity Framework 創建對象時完全有效,但在代碼使用者創建對象時可能會失敗。
確保代碼使用者始終創建有效對象的解決方案是使用靜態工廠方法:
保護默認構造函數。 實體框架適用於受保護的構造函數。
添加創建空對象(例如User
對象)的靜態工廠方法,在創建后設置所有屬性(例如Addresses
和License
並返回完全構造的User
對象
這樣,實體框架使用受保護的默認構造函數從從某些數據源獲得的數據創建有效對象,而代碼使用者使用靜態工廠方法創建有效對象。
我使用這個答案為什么我的實體框架代碼優先代理集合為空,為什么我不能設置它?
構造函數初始化有問題。 我這樣做的唯一原因是使測試代碼更容易。 確保集合永遠不會為空可以節省我在測試等中不斷初始化
其他答案完全回答了這個問題,但我想補充一些東西,因為這個問題仍然相關並且會出現在谷歌搜索中。
當您在 Visual Studio 中使用“數據庫中的代碼第一個模型”向導時,所有集合都像這樣初始化:
public partial class SomeEntity
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public SomeEntity()
{
OtherEntities = new HashSet<OtherEntity>();
}
public int Id { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<OtherEntity> OtherEntities { get; set; }
}
我傾向於認為向導輸出基本上是 Microsoft 的官方推薦,因此我為什么要添加到這個 5 年前的問題。 因此,我將所有集合初始化為HashSet
。
就我個人而言,我認為調整上述內容以利用 C# 6.0 的自動屬性初始值設定項會非常巧妙:
public virtual ICollection<OtherEntity> OtherEntities { get; set; } = new HashSet<OtherEntity>();
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.