【问题标题】:Entity Framework - Include Take Top N for each parent [duplicate]实体框架 - 包括每个父级的前 N ​​个 [重复]
【发布时间】:2015-12-15 05:58:45
【问题描述】:

我正在实施一个论坛,每个帖子都可以有 cmets。

我想加载一批 N 个帖子,因此每个帖子都会加载前 K 个 cmets。

我已经开始使用此代码,但它目前包含 所有 每个帖子的 cmets。 如何只为每个帖子加载前 K 个 cmets,而不必对数据库进行 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();

谢谢!

【问题讨论】:

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


    【解决方案1】:

    我认为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
    }
    

    编辑:(使其与 db 的往返行程一起工作)

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

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

    【讨论】:

    • 这是一种方法,但它仍然使用多个数据库调用,不是吗?我希望有一种方法可以通过使用一些 linq 语法来避免对数据库的 N 次调用,该语法会使用巫术将其转换为单个 SQL 语句。
    • @Paceman 是的,你是对的,它需要多次往返 db。但正如我所说,目前没有办法使用Include 作为你想要的。但是我不太确定,让我们看看其他人是否可以提供一些建议。
    【解决方案2】:

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

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

    【讨论】:

    • 是的,谢谢,我投了那个票。
    • @Paceman 看起来链接中的解决方案适用于您的情况(我认为这涉及 一对多 关系)。我测试的涉及多对多关系,它不起作用。
    • EF Core 有什么新东西吗?
    猜你喜欢
    • 1970-01-01
    • 2012-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-21
    • 1970-01-01
    相关资源
    最近更新 更多