Entity Framework v6.1 高效加载深度相关实体,然后对其进行查询

Posted

技术标签:

【中文标题】Entity Framework v6.1 高效加载深度相关实体,然后对其进行查询【英文标题】:Entity Framework v6.1 Load Deep Related Entities Efficiently And Then Query On Them 【发布时间】:2014-11-13 16:04:03 【问题描述】:

我有以下实体。类别、主题、帖子、成员。它们通过以下方式相关

Category 有一个 Topic 列表 主题有帖子列表 帖子有会员

以下是类

public class Category

    public Guid Id  get; set; 
    public string Name  get; set; 
    public string Description  get; set; 
    public virtual IList<Topic> Topics  get; set; 


public class Topic

    public Guid Id  get; set; 
    public string Name  get; set; 
    public DateTime CreateDate  get; set; 
    public virtual Category Category  get; set; 
    public virtual IList<Post> Posts  get; set; 
    public virtual MembershipUser User  get; set; 


public class Post

    public Guid Id  get; set; 
    public string PostContent  get; set; 
    public DateTime DateCreated  get; set; 
    public DateTime DateEdited  get; set; 
    public virtual Topic Topic  get; set; 
    public virtual MembershipUser User  get; set; 


public class MembershipUser

    public Guid Id  get; set; 
    public string UserName  get; set; 

    etc....


我希望能够高效地执行以下查询

    获取类别中的最新帖子,包括发布帖子的成员(按 CategoryId) 获取主题中的最新帖子,包括发布帖子的成员(按 TopicId)

我一直在使用以下 Include() - 但我想知道是否有更有效的方法来执行此操作...?

查询 1

_context.Category
     .Where(x => x.Id == categoryId)
     .Include(x => x.Topics.Select(p => p.Posts.Select(u => u.User)))
     .SelectMany(x => x.Topics)
     .SelectMany(x => x.Posts)
     .OrderByDescending(x => x.DateCreated)
     .FirstOrDefault();

查询 2

_context.Topic
       .Where(x => x.Id == topicId)
       .Include(x => x.Posts.Select(u => u.User))
       .SelectMany(x => x.Posts)
       .OrderByDescending(x => x.DateCreated)
       .FirstOrDefault();

非常感谢任何帮助或指点。

【问题讨论】:

【参考方案1】:

如果您正在寻找高效的性能,您可能有兴趣编写一个非常简单的 MARS 存储过程,该过程包含您想要的所有数据。您可以对每个结果集使用 Translate 函数来具体化模型对象。 Entity Framework 将自动修复您的导航属性。

http://msdn.microsoft.com/en-us/data/jj691402.aspx

如果您不想创建 proc,执行多个简单查询通常会更有效。我经常使用内存中的 ID 列表过滤 linq 到实体查询,如下所示:qry.where(x=>list.contains(x.Id))。

自 2014 年 9 月 21 日起编辑

大多数开发人员认为高效查询是一种执行速度快且只返回您需要的数据的查询。这几乎是真的。但是,高效的数据访问层是重用有限数量的快速执行查询的层。有时,开发人员会以自己的方式试图使每个单独的查询尽可能高效,却没有意识到它们正在导致 sql server 管理过多的执行计划并降低整体性能。我建议您尝试对给定表使用两种或三种方法。我将从一个查询开始,该查询返回一个包含相关数据的主题,一个返回包含此场景所需数据的主题列表。

以下方法将进入您的 DataContext 类:

public Topic GetTopic(int topicId) 

      return this.Topics.Include("Posts.User").Single(x => x.Id = topicId);

这可以放在你的主题类中:

public Post GetMostRecentPost()

    return this.Posts.OrderByDescending(x => x.DateCreated).FirstOrDefault();

或者,如果您实际上只想获取最新的帖子,并且从未发现自己需要查询主题及其所有帖子,您可以在您的上下文中使用以下查询。

public Post GetMostRecentPost(int topicId)

  return this.Posts.Include(x => x.Topic).Include(x=>x.User).where(x => x.TopicId == topicId).OrderByDescending(x => x.DateCreated).FirstOrDefault();

作为一般经验法则,如果您尝试返回 Post,最好使用 context.Post 开始查询,并尝试以此构建您的查询。尽量避免像 select 或 selectman 这样的投影查询,除非您打算返回匿名对象并且愿意执行 sql 分析以确保查询看起来符合预期。

【讨论】:

感谢您的评论。我试图避免存储过程。另外,我尝试使用 contains() 对 ID 执行多个简单查询,但查询似乎更慢? 再次查看您的查询,我意识到您有一个非常简单的查询需求。三张桌子不是很多。试试这个: context.Topics.Include("Posts.User").Single(x=>x.Id=topicId).selectmany(x=>x.Posts).orderbydescending(x=>x. DateCreated).FirstOrDefault() 要记住的重要一点是使查询尽可能简单,并在检索到数据后在内存中进行投影。如果上述查询不起作用,请告诉我。 感谢您的示例。这几乎正​​是我上面所拥有的。尽管您的 include 在 Single() 查询之前。我认为它应该是为了提高效率(参见官方文档链接)? msdn.microsoft.com/en-gb/data/jj574232.aspx - 另外你为什么使用 Single() 而不是 FirstOrDefault()?谢谢【参考方案2】:

首先,您应该从测量当前查询时间开始。考虑到 FirstOrDefault() 我希望这个查询运行得非常快。

我通常使用 Sql Profiler 来处理这些事情。在 Web 应用程序中,我通常也有 StackExchange.MiniProfiler 或 Glimpse。两者都可以连接到 EF 以提供准确的查询时间。

Include 的问题在于 EF 在连接数据方面确实很糟糕,因为它们使用连接数据而不是加载多个集合。我写了一个blog post about it,其中包括数字和可能的解决方法。

但总结一下我的发现是,连接策略的糟糕程度取决于数据的形状。如果您将表 A 中的一行与表 B 中的一行连接起来,就像这里的情况,没有问题。当您加载很少的实体或加载的实体之一非常小时,它也几乎不会引起注意。

在您的情况下,由于您只是在寻找最上面的帖子,我会考虑的唯一优化是投影数据,这样您就不会加载您可能不需要的属性。但很可能你所做的任何事情都只有几微秒。

不过,我从 twitter 对话中知道这是一个只读场景。这使得可以将 AsNoTracking() 添加到查询中,从而减少 dbcontext 的工作量(这是对应用服务器上的 CPU 和内存的改进,而不是数据库)。

所以,测量它。我希望这将在 db 内的

更新:再次仔细阅读我意识到我的头脑产生了错误的查询计划,您可能可以通过将包含移动到最后一个 .SelectMany(x => x.Posts) 并更改来改进性能并减少连接它到 .Include(post => post.User) 让您只加载帖子和用户,而不加载类别和主题。它仍然会加入这些但不加载数据。

UPDATE2:如何编写 Query1 的示例。我不确定是否有区别,但我希望它可能会减少加载的数据。您必须查看分析器。

_context.Category .AsNoTracking() .Where(x => x.Id == categoryId) .SelectMany(x => x.Topics) .SelectMany(x => x.Posts) .Include(x => x.User) .OrderByDescending(x => x.DateCreated) .FirstOrDefault(); 如果查询按我预期的那样运行,那应该非常接近最佳查询。就像我说的。您需要检查生成的查询才能真正了解。

【讨论】:

嗨,米凯尔,感谢您的评论。查询1呢?在您的博客文章中,您只介绍了一个级别的包含。但是,如果我要说尝试做一个加载所有集合并将它们合并到内存中的建议......这会是你的方法吗? 在加载单个实体时,您真的不需要经历所有这些麻烦。无论如何,这将非常快。检查新的更新,了解我将如何编写 Q1

以上是关于Entity Framework v6.1 高效加载深度相关实体,然后对其进行查询的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework Core:高效的数据库更新

Entity Framework Core 7中高效地进行批量数据插入

如何在 Entity Framework 6.1 中仅加载子对象的某些字段?

Entity Framework 学习系列 - 认识理解Entity Framework

Entity Framework 7 支持批量操作和 JSON 列

MVC5 Entity Framework学习之Entity Framework高级功能