在Entity Framework Core中编写实体POCO类的正确方法是什么?

问题描述:

默认情况下,EF Core有一个“代码优先思路”,即它应该以代码优先的方式使用,即使支持数据库优先方法,它也被描述为对现有方法进行反向工程数据库并创建它的代码优先表示。我的意思是,在代码中“手工”(代码优先)创建并从数据库(通过Scaffold-DbContext命令)生成的模型(POCO类)应该是相同的。在Entity Framework Core中编写实体POCO类的正确方法是什么?

令人惊讶的是,官方EF核心文件证明了显着的差异。下面是在代码中创建的模型的例子:从现有的数据库https://ef.readthedocs.io/en/latest/platforms/aspnetcore/new-db.html这里是的例子逆向工程是:https://ef.readthedocs.io/en/latest/platforms/aspnetcore/existing-db.html

这是在第一种情况下的实体类:

public class Blog 
{ 
    public int BlogId { get; set; } 
    public string Url { get; set; } 

    public List<Post> Posts { get; set; } 
} 

public class Post 
{ 
    public int PostId { get; set; } 
    public string Title { get; set; } 
    public string Content { get; set; } 

    public int BlogId { get; set; } 
    public Blog Blog { get; set; } 
} 

,这是第二种情况下的实体类:

public partial class Blog 
{ 
    public Blog() 
    { 
     Post = new HashSet<Post>(); 
    } 

    public int BlogId { get; set; } 
    public string Url { get; set; } 

    public virtual ICollection<Post> Post { get; set; } 
} 

第一个例子是一个非常简单,非常明显的POCO类。它在文档中随处可见(数据库生成的示例除外)。第二个例子,虽然,有一些补充:

  • 类被声明为部分(即使有无处可看它的另一个部分定义)。
  • 导航属性的类型为ICollection < T>,而不仅仅是列表< T>。
  • 导航属性初始化为新的HashSet < T>()在构造函数中。代码优先例子中没有这样的初始化。
  • 导航属性被声明为虚拟
  • 生成的上下文类中的DbSet成员也是虚拟

我已经试过从数据库(本文写作时最新的工具)脚手架模型,它生成的实体完全如图所示,所以这不是一个过时的文档问题。所以官方的工具生成不同的代码,官方文档建议编写不同的(不重要的)代码 - 没有部分类,虚拟成员,构造初始化等。

我的问题是,试图在代码中构建模型,应该如何我写我的代码?我喜欢使用ICollection而不是List,因为它更通用,但除此之外,我不确定是否需要关注文档或MS工具?我需要将它们声明为虚拟吗?我需要在构造函数中初始化它们吗?等等......

我从旧时代EF知道,虚拟导航属性允许延迟加载,但它不支持连(尚)在EF核心,我不知道任何其他用途。也许它会影响性能?也许工具尝试生成面向未来的代码,这样,当延迟加载将被执行,在POCO类和上下文就能支持呢?如果是这样,我能够摆脱他们,因为我并不需要懒加载(所有数据查询是在回购封装的)?

不久,请帮助我了解为什么差别,以及风格应该在代码构建模型时怎么用?

+1

关于第3点,根据我自己最近项目的经验...我强烈建议不要初始化它们以清空构造函数中的集合。原因在于,在许多情况下,您的业务逻辑需要能够确定明细集合是空的还是尚未加载。 –

我试着给一个简短的回答每一个点你提到

  • partial类的工具生成的代码特别有用。假设你想实现一个仅模型的派生属性。对于代码第一,你只要做到这一点,无论你想要的。对于数据库,如果更新模型,则类文件将被重写。因此,如果您想保留扩展代码,您希望将其放置在托管模型之外的其他文件中 - 这是partial可帮助您在不调整自动生成的代码的情况下扩展该类的地方。

  • ICollection绝对是一个合适的选择,即使对于第一代码。没有排序声明,您的数据库可能不会支持已定义的订单。

  • 构造函数初始化至少是方便......假设你有一个空收集数据库明智的,或者你没有加载所有的财产。没有构造函数,你必须明确地在代码中的任意点处理null个案。无论你是否应该使用ListHashSet,我现在都无法回答。

  • virtual为数据库实体启用代理创建,这可以帮助您做两件事情:如您已经提到的延迟加载并更改跟踪。代理对象可以使用setter立即跟踪对虚拟属性的更改,而上下文中的普通对象需要在SaveChanges上进行检查。在某些情况下,这可能会更有效(通常不会)。

  • virtual IDbSet背景下的条目允许单元测试的测试,样机环境设计更容易。其他用例也可能存在。

+1

请注意,EF Core还没有延迟加载(或更改跟踪)代理。我真的不知道为什么RevEng生成虚拟导航道具。 – bricelam

+0

我已经提交了问题[#6326](https://github.com/aspnet/EntityFramework/issues/6326)与团队讨论了使用虚拟技术。 – bricelam

+0

大多数藏品都由独特的价值构成。例如,'List '集合中不会有两个相同的条目。因此,HashSet通常是表示这种情况的语义上正确的选择。 –