繁体   English   中英

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

[英]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; }
}

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

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

收藏:没关系。

作为导航属性的集合和引用之间存在明显区别。 引用一个实体。 集合包含实体。 这意味着初始化集合在业务逻辑方面毫无意义:它没有定义实体之间的关联。 设置参考确实如此。

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

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

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

实体框架核心

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

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

因此,总而言之,同样在 EF-core 中,在构造函数中初始化引用导航属性可能会导致麻烦。 不要这样做。 反正也没意义。

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

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

第二个例子更好,因为

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

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

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

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

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

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

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

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

  1. 我使用 emty children collection构建parent entity
  2. 然后我将参考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:哪个更好? 为什么? 利弊?

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

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

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

第一个变体在 Entity Framework 创建对象时完全有效,但在代码使用者创建对象时可能会失败。

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

  1. 保护默认构造函数。 实体框架适用于受保护的构造函数。

  2. 添加创建空对象(例如User对象)的静态工厂方法,在创建后设置所有属性(例如AddressesLicense并返回完全构造的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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM