EF codefirst:我应该初始化导航属性吗?

Ima*_*sab 59 c# domain-driven-design entity-framework navigation-properties ef-code-first

我曾经看过一些书(例如编程实体框架代码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; }
}
Run Code Online (Sandbox Code Playgroud)

生成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; }
}
Run Code Online (Sandbox Code Playgroud)

Q1:哪一个更好?为什么?优点和缺点?

编辑:

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; }
}
Run Code Online (Sandbox Code Playgroud)

Q2:在第二种方法中,如果`License`类也引用了'User`类,则会出现堆栈溢出.这意味着我们应该有单向引用.(?)我们应该如何决定应该删除哪一个导航属性?

Ger*_*old 72

收藏:没关系.

集合和引用之间存在明显差异,即导航属性.引用一个实体.集合包含实体.这意味着在业务逻辑方面初始化集合是没有意义的:它没有定义实体之间的关联.设置参考确实.

因此,无论是否或如何初始化嵌入式列表,都只是一个偏好问题.

至于"如何",有些人更喜欢延迟初始化:

private ICollection<Address> _addresses;

public virtual ICollection<Address> Addresses
{ 
    get { return this._addresses ?? (this._addresses = new HashSet<Address>());
}
Run Code Online (Sandbox Code Playgroud)

它可以防止空引用异常,因此它有助于单元测试和操作集合,但它也可以防止不必要的初始化.当一个类有相对多的集合时,后者可能会有所不同.缺点是它需要相对多的管道,尤其是.与没有初始化的自动属性相比.此外,C#中的零传播运算符的出现使得初始化集合属性变得不那么紧迫.

...除非应用显式加载

唯一的问题是初始化集合使得很难检查Entity Framework是否加载了集合.如果一个集合被初始化,那么声明如...

var users = context.Users.ToList();
Run Code Online (Sandbox Code Playgroud)

...将创建User具有空的非空集合的对象Addresses(延迟加载).检查是否加载了集合需要像......这样的代码

var user = users.First();
var isLoaded = context.Entry(user).Collection(c => c.Addresses).IsLoaded;
Run Code Online (Sandbox Code Playgroud)

如果未初始化集合,则null可以执行简单检查.因此,当选择性显式加载是编码实践的重要部分时,即......

if (/*check collection isn't loaded*/)
    context.Entry(user).Collection(c => c.Addresses).Load();
Run Code Online (Sandbox Code Playgroud)

...初始化集合属性可能更方便.

参考属性:不要

引用属性是实体,因此为它们分配空对象是有意义的.

更糟糕的是,如果您在构造函数中启动它们,EF在实现对象或延迟加载时不会覆盖它们.在您主动替换它们之前,它们将始终具有初始值.更糟糕的是,您甚至可能最终在数据库中保存空实体!

还有另一个影响:关系修复不会发生.关系修正是EF通过其导航属性连接上下文中的所有实体的过程.当a User和a Licence分别加载时,仍将User.License填充,反之亦然.当然,除非License在构造函数中初始化.对于1:n关联也是如此.如果在其构造函数中Address初始化a User,则User.Addresses不会填充!

实体框架核心

Entity Framework核心中的关系修正(在编写本文时为2.1)不受构造函数中初始化的引用导航属性的影响.也就是说,当单独从数据库中提取用户和地址时,将填充导航属性.
然而,延迟加载并没有覆盖初始化参考导航性能.因此,总而言之,在EF-core中初始化构造函数中的引用导航属性可能会带来麻烦.不要这样做.无论如何,这没有意义,

  • 不,因为使用默认对象设置引用属性没有用.这些引用具有业务含义,因此它们应该是"null"或引用有意设置的有意义的对象. (3认同)
  • 题外话,今天晚些时候,但是,为什么你要在这里特别使用 HashSet 呢? (2认同)
  • @stovroz这是几个选项之一.HashSet针对快速查找进行了优化,并保证包含唯一对象.EF默认使用t4或代码优先从数据库生成HashSet. (2认同)

Ily*_*kin 6

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

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

第二个例子更好,因为

  • 您确定该实体已设置所有属性
  • 你避免愚蠢 NullReferenceException
  • 你让你的代码的消费者更快乐

实践领域驱动设计的人们将集合公开为只读,并避免使用 setter。(请参阅NHibernate 中只读列表的最佳实践是什么

Q1:哪个更好?为什么?利弊?

最好公开非空集合,因为您可以避免在代码中进行额外检查(例如Addresses)。在您的代码库中拥有一份很好的合同。但是我可以公开对单个实体的可空引用(例如License

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

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

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

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

  1. parent entity用 emty构建children collection
  2. 然后我添加 eveychild参考parent entityinto 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; }
}
Run Code Online (Sandbox Code Playgroud)