实体框架v6.1有效地加载深层相关实体然后查询它们

时间:2014-09-19 06:15:15

标签: sql entity-framework linq-to-entities entity-framework-6

我有以下实体。类别,主题,发布,成员。它们与以下相关

  • 类别有主题列表
  • 主题有一个帖子列表
  • Post有会员

以下是课程

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....

}

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

  1. 获取类别中的最新帖子,包括发布帖子的成员(按CategoryId)
  2. 获取主题中的最新帖子,包括发布帖子的成员(By TopicId)
  3. 我一直在使用以下内容与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();
    

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

2 个答案:

答案 0 :(得分:4)

如果您正在寻找高效的性能,您可能有兴趣编写一个包含您想要的所有数据的非常简单的MARS存储过程。您可以在每个结果集上使用“转换”功能来实现模型对象。实体框架将自动修复您的导航属性。

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

如果您不想创建proc,则执行多个简单查询通常更有效。我经常使用内存中的Ids过滤linq到实体查询,如下所示:qry.where(x =&gt; 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();
}

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

答案 1 :(得分:2)

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

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

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

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

在您的情况下,由于您只是在寻找热门帖子,我所看到的唯一优化是投放数据,因此您不会加载您可能不需要的属性。但最有可能你做的事只有几微秒。

尽管如此,我从Twitter的谈话中知道这是一个只读的场景。这使得可以将AsNoTracking()添加到查询中,这使得dbcontext更少工作(这是对应用服务器上的cpu和内存的改进,而不是数据库)。

所以,衡量它。我希望这能在db +内部的<1ms内运行一些传输时间,并且没有那么值得改进。可能更好地添加缓存。

更新:再次仔细阅读我意识到我的头脑产生了错误的查询计划,你可以通过将include移动到最后一个之后来改善perf和减少连接.SelectMany(x =&gt; x.Posts)和将其更改为.Include(post =&gt; 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(); 如果查询按预期运行,那应该非常接近最佳查询。就像我说的。您需要检查生成的查询才能真正了解。