我正在实施一个论坛,每个帖子都可以发表评论。
我想加载一批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();
谢谢!
答案 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()
之后)。