EF 代码优先: 我应该初始化导航属性吗?

我见过一些书(例如 编程实体框架代码)定义它们的域类(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 时初始化类的导航属性,如:

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; }
}

问题1: 哪一个更好? 为什么? 优点和缺点?

编辑:

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; }
}

Q2: 在第二种方法中,如果“许可”类也引用了“用户”类,那么就会出现堆栈溢出。意思是我们应该有单向引用。(?)我们应该如何决定哪一个导航属性应该删除?

31174 次浏览

这是多余的 new的名单,因为你的 POCO 是依赖于懒惰装载。

延迟加载是指在第一次访问引用实体/实体的属性时,从数据库自动加载实体或实体集合的过程。在使用 POCO 实体类型时,延迟加载是通过创建派生代理类型的实例,然后重写虚拟属性以添加加载钩子来实现的。

如果您要删除虚拟修饰符,那么您将关闭延迟加载,在这种情况下,您的代码将不再工作(因为没有任何东西会初始化列表)。

注意,惰性加载是实体框架支持的一个特性,如果您在 DbContext 的上下文之外创建类,那么依赖的代码显然会受到 NullReferenceException的影响

高温

我用这个 为什么我的实体框架代码第一代理集合为空,为什么我可以设置它?的答案

构造函数初始化有问题。我这样做的唯一原因是为了使测试代码更容易。确保集合永远不为空节省了我不断初始化在测试等

在我所有的项目 我遵循规则——“集合不应该为空。它们要么为空,要么有值。”

当创建这些实体是第三方代码(例如 ORM)的责任,并且您正在从事一个短时间的项目时,可能会有第一个示例。

第二个例子更好,因为

  • 确保该实体已设置了所有属性
  • 你避免愚蠢的 NullReferenceException
  • 您可以使代码的使用者感到更快乐

练习领域驱动设计的人将集合公开为只读并避免在其上设置 setter

问题1: 哪一个更好? 为什么? 优点和缺点?

最好公开非空集合,因为这样可以避免在代码中进行额外的检查(例如 Addresses)。这是一个很好的契约,在您的代码库。但是对于我来说,暴露单个实体的可空引用(例如 License)是可以的

Q2: 在第二种方法中,如果 License类也有对 User类的引用,那么就会出现堆栈溢出。意思是我们应该有单向引用。(?)我们应该如何决定哪一个导航属性应该删除?

当我自己开发 数据映射器模式数据映射器模式的时候,我尽量避免双向引用,并且很少从孩子到父母的引用。

当我使用 ORM 时,很容易有双向引用。

当需要使用双向参考集为我的单元测试构建测试实体时,我遵循以下步骤:

  1. 我用空的 children collection构建 parent entity
  2. 然后,我将每个 child与参考 parent entity添加到 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; }
}

收集: 这不重要。

作为导航属性的集合和引用之间有明显的区别。引用 一个实体。集合 包含实体。这意味着就业务逻辑而言,集合的初始化是 毫无意义: 它不定义实体之间的关联。设置引用可以。

因此,是否初始化嵌入式列表或如何初始化嵌入式列表纯粹是一个偏好问题。

至于“如何”,有些人更喜欢惰性初始模式:

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();

... 将创建具有空的、非空的 Addresses集合的 User对象(延迟加载除外)。检查集合是否已加载需要如下代码..。

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 通过导航属性连接上下文中所有实体的过程。当分别加载 UserLicence时,仍将填充 User.License,反之亦然。当然,除非 License是在构造函数中初始化的。对于1: n 关联也是如此。如果 Address将在其构造函数中初始化 User,则不会填充 User.Addresses

实体框架核心

实体框架核心中的关系修复(编写本文时为2.1)不受构造函数中初始化的引用导航属性的影响。也就是说,当用户和地址分别从数据库中提取时,将填充导航属性。
但是,延迟加载会覆盖初始化的引用导航属性。

在 EF-core 3中,初始化引用导航属性会阻止 Include正常工作。

因此,总之,在 EF-core 中,在构造函数中初始化引用导航属性可能会引起麻烦。别这么做。反正也说不通。

问题1: 哪一个更好? 为什么? 优点和缺点?

在实体构造函数中设置虚属性时的第二种变体有一个明确的问题,称为“ 构造函数中的虚成员调用”。

至于没有初始化导航属性的第一种变体,有两种情况取决于谁/什么创建了一个对象:

  1. 实体框架创建一个对象
  2. 代码使用者创建一个对象

第一个变量在实体框架创建对象时是完全有效的, 但是当代码使用者创建对象时可能会失败。

确保代码使用者始终创建有效对象的解决方案是使用 静态工厂法静态工厂法:

  1. 保护缺省构造函数。实体框架可以与受保护的构造函数一起工作。

  2. 添加一个静态工厂方法,该方法创建一个空对象,例如 User对象,在创建后设置所有属性,例如 AddressesLicense,并返回一个完全构造好的 User对象

这样,实体框架使用一个受保护的缺省构造函数,从一些数据源获得的数据中创建一个有效的对象,而代码使用者使用一个静态工厂方法来创建一个有效的对象。

其他的答案完全回答了这个问题,但是我想补充一些东西,因为这个问题仍然是相关的,并出现在谷歌搜索。

在 VisualStudio 中使用“数据库中的代码优先模型”向导时,所有集合的初始化方式如下:

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

就我个人而言,我认为利用 C # 6.0的自动属性初始化器来调整以上内容是相当聪明的:

    public virtual ICollection<OtherEntity> OtherEntities { get; set; } = new HashSet<OtherEntity>();