实体框架 - 包括每个父母的前N个

时间:2015-09-18 07:16:29

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

我正在实施一个论坛,每个帖子都可以发表评论。

我想加载一批N个帖子,以便每个帖子加载前K个评论。

我已经开始使用此代码了,但它目前包含所有每个帖子的评论。 如何只为每个帖子加载前K个评论,而不必对数据库进行K调用?

List<ForumPost> result = ctx
                            .ForumPosts
                            .Include("Comments") // <-- ??? How to take first K ???
                            .Where(i => i.Thread.ID == threadID)
                            .OrderByDescending(i => i.Date)
                            .Take(N).ToList();

谢谢!

2 个答案:

答案 0 :(得分:5)

目前,在EF中,您无法以任何方式过滤或限制Include d相关实体。

如果您需要避免多个查询,那么有解决方案,但它们都涉及直接在SQL端工作。您可以使用:TVF(表值函数),存储过程,视图或简单查询。在每种情况下,都有不同的解决方案将结果映射到您的实体。并且映射的实体将是不可跟踪的,因此您无法修改它们并将它们写回服务器(除非您将它们显式添加到现有的DbContext

您可以阅读Julie Lerman撰写的这篇文章,了解我所说的内容:Use Projections and a Repository to Fake a Filtered Eager Load

您可以在Data User's voice投票支持相关功能。查找&#34;允许过滤包含扩展方法&#34;。最有可能的是,如果实现了这一点,在这种情况下也可以使用.Take

有关此功能演变的更多信息,请查看EF Core github issue 1833: Support filtered Include。在那里你可以找到有趣的东西:

答案 1 :(得分:1)

我认为Include目前没有该过滤功能。您可以尝试使用显式加载。但是,通过这种方式查询看起来不太好,我会在foreach列表后使用ForumPost

List<ForumPost> result = ctx
                        .ForumPosts                            
                        .Where(i => i.Thread.ID == threadID)
                        .OrderByDescending(i => i.Date)
                        .Take(N).ToList();
//start loading top K comments from each post
foreach(var post in result){
    ctx.Entry<ForumPost>(post).Collection("Comments")
                              .Query()
                              .Take(k);//assume k is a constant
}

编辑 :(使其适用于db的一次往返)

重复的问题有一个答案,但是如果关系是 多对多 ,它就无效了,至少那是我在中测试的那个EF6 即可。对于 多对多 关系,我尝试找到一个解决方案并最终得到以下代码,但可能会在您需要的客户端进行一些权衡一个循环,将所有RelationshipEntries的状态重置为Unchanged,以模仿它们都是从数据库加载的。

ctx.Configuration.LazyLoadingEnabled = false;
var stateManager = ((IObjectContextAdapter)ctx).ObjectContext.ObjectStateManager;
var result = ctx.ForumPosts.Where(i => i.Thread.ID == threadID)
                .Select(e => new { e, Comments = e.Comments.Take(k) })
                .AsEnumerable()
                .Select(e => {
                   //set the Comments manually
                   e.e.Comments = e.Comments;
                   //Reset RelationshipEntries' state
                   foreach(var c in e.Comments) {
                      stateManager.ChangeRelationshipState(e.e, c, o => o.Comments, 
                                                           EntityState.Unchanged);
                   }
                   return e.e;
                }).ToList();

加载的结果甚至缓存到Local中。现在它只进行一次往返db(在调用AsEnumerable()之后)。