Entity Framework - 为每个父项包括 Take Top N

Entity Framework - Include Take Top N for each parent

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

我想加载一批 N post,这样每个 post 加载前 K 条评论。

我有此代码作为开始,但它目前包含 所有 个注释,每个 post。 如何只加载每个 post 的前 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();

谢谢!

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

列表后使用 foreach
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
}

编辑:(使其与数据库往返一次即可工作)

重复的问题对此有一个答案,但如果关系是 多对多,则它不起作用,至少是这样根据我在 EF6 中的测试。对于 many-to-many 关系,我尝试找到一个解决方案并最终得到以下有效但可能存在一些权衡的代码在客户端,您需要一个循环来将所有 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();

加载的结果甚至缓存到本地。现在它只往返于数据库一次(在调用 AsEnumerable() 之后)。

(目前,在 EF 中您无法以任何方式过滤或限制 Included 个相关实体)。从 5.0 开始在 EF Core 中是可能的:Filtered include.

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

您可以阅读 Julie Lerman 的这篇文章以了解我在说什么:Use Projections and a Repository to Fake a Filtered Eager Load

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

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