我曾经看过一些书籍(例如编程实体框架代码,首先是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; }
}
生成POCO时,其他一些书籍或工具(例如 Entity Framework Power Tools )初始化类的导航属性,如:
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; }
}
答案 0 :(得分:69)
作为导航属性,集合和引用之间存在明显差异。引用是一个实体。集合包含实体。这意味着在业务逻辑方面初始化集合无意义:它不定义实体之间的关联。设置参考确实。
因此,无论是否或如何初始化嵌入式列表,都只是一个偏好问题。
对于“如何”,有些人更喜欢延迟初始化:
private ICollection<Address> _addresses;
public virtual ICollection<Address> Addresses
{
get { return this._addresses ?? (this._addresses = new HashSet<Address>());
}
它可以防止空引用异常,因此它便于单元测试和操作集合,但它也可以防止不必要的初始化。当一个类有相对多的集合时,后者可能会有所不同。缺点是它需要相对多的管道,尤其是。与没有初始化的自动属性相比。此外,C#中的零传播运算符的出现使得初始化集合属性变得不那么紧迫。
...除非应用显式加载
唯一的问题是初始化集合使得很难检查Entity Framework是否加载了集合。如果集合已初始化,则声明如...
var users = context.Users.ToList();
...将创建具有空的非空User
集合的Addresses
个对象(延迟加载)。检查是否加载了集合需要像......
var user = users.First();
var isLoaded = context.Entry(user).Collection(c => c.Addresses).IsLoaded;
如果未初始化集合,则会执行简单的null
检查。因此,当选择性显式加载是编码实践的重要部分时,即......
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
!
实体框架核心
Entity Framework核心中的关系修正(编写本文时为2.1)不受构造函数中初始化的引用导航属性的影响。也就是说,当单独从数据库中提取用户和地址时,将填充导航属性 但是,延迟加载不会覆盖初始化的引用导航属性。因此,总而言之,在EF-core中初始化构造函数中的引用导航属性可能会带来麻烦。不要这样做。无论如何它都没有意义,
答案 1 :(得分:4)
在我的所有项目中我遵循规则 - “集合不应该为空。它们是空的或有值。”
当创建这些实体是第三方代码(例如ORM)并且您正在处理短期项目时,可能会有第一个示例。
第二个例子更好,因为
NullReferenceException
执行域驱动设计的人员将集合公开为只读,并避免使用setter。 (见What is the best practice for readonly lists in NHibernate)
Q1:哪一个更好?为什么?优点和缺点?
最好公开非空的colections,因为你避免在代码中进行额外的检查(例如Addresses
)。拥有代码库是一份很好的合同。但是我可以将可以为空的引用暴露给单个实体(例如License
)
Q2:如果License
类也引用User
类,则第二种方法会出现堆栈溢出。这意味着我们应该有单向引用。(?)我们应该如何决定应该删除哪一个导航属性?
当我自己开发data mapper pattern时,我试图避免双向引用,并且很少提到从孩子到父母的引用。
当我使用ORM时,很容易有双向引用。
当需要使用双向参考集为我的单元测试构建测试实体时,我遵循以下步骤:
parent entity
构建children collection
。child
的{{1}}添加到parent entity
。如果在children collection
类型中设置无参数构造函数,我将需要License
属性。
user
答案 2 :(得分:3)
列表的new
是多余的,因为您的POCO取决于延迟加载。
延迟加载是在第一次访问引用实体/实体的属性时从数据库自动加载实体或实体集合的过程。使用POCO实体类型时,通过创建派生代理类型的实例然后重写虚拟属性以添加加载挂钩来实现延迟加载。
如果要删除虚拟修饰符,则会关闭延迟加载,在这种情况下,您的代码将不再起作用(因为没有任何内容可以初始化列表)。
请注意,延迟加载是实体框架支持的功能,如果您在DbContext的上下文之外创建类,则依赖代码显然会受到NullReferenceException
HTH
答案 3 :(得分:3)
Q1:哪一个更好?为什么?优点和缺点?
在实体构造函数中设置虚拟属性时的第二个变体有一个明确的问题,称为“Virtual member call in a constructor”。
对于没有初始化导航属性的第一个变体,有两种情况取决于创建对象的人/是什么:
当Entity Framework创建对象时,第一个变体完全有效, 但是当代码使用者创建对象时可能会失败。
确保代码使用者始终创建有效对象的解决方案是使用static factory method:
使默认构造函数受到保护。实体框架可以与受保护的构造函数一起使用。
添加一个静态工厂方法,用于创建一个空对象,例如一个User
对象,设置所有属性,例如Addresses
和License
,在创建后返回完整构建的User
对象
这样,Entity Framework使用受保护的默认构造函数从某些数据源获取的数据创建有效对象,代码使用者使用静态工厂方法创建有效对象。
答案 4 :(得分:2)
我使用了这个Why is my Entity Framework Code First proxy collection null and why can't I set it?
的答案构造函数启动存在问题。我这样做的唯一原因是使测试代码更容易。确保集合永远不会为我节省不了我在测试中不断初始化
答案 5 :(得分:1)
其他答案完全回答了这个问题,但我想补充一些内容,因为这个问题仍然存在,并且会出现在谷歌搜索中。
在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; }
}
我倾向于将向导输出基本上作为微软的官方推荐,因此我为什么要添加这个为期五年的问题。因此,我会将所有馆藏初始化为HashSet
s。
就个人而言,我认为调整上述内容以利用C#6.0的自动属性初始化程序非常灵活:
public virtual ICollection<OtherEntity> OtherEntities { get; set; } = new HashSet<OtherEntity>();