EF 核心 2.1 使用投影

EF core 2.1 using take in projection

我想在投影中使用 Take() 但我不想生成 N+1 查询,此外在投影中没有 Take() 我面临性能问题。 我将 Take() 与 EF6 一起使用,但我在 EF Core 上遇到了 N+1 问题。

示例投影:

source.Select(post => new PostProject
            {
                PostDisableCoins = post.PostDisableCoins
                                    .OrderBy(x=>x.CoinAmount)
                                    .Take(3)
                                    .ToList(),
                WarStartTime = post.WarStartTime,
                WarEndTime = post.WarEndTime,
                WarWinner = post.WarWinner,
                WarDeclarer = post.WarDeclarer
            }); 

我想要没有 N+1 的 Take(3),有什么建议吗?!?

请注意有关 EF core 2.1 new features:

的文档

We have improved our query translation to avoid executing "N + 1" SQL queries in many common scenarios in which the usage of a navigation property in the projection leads to joining data from the root query with data from a correlated subquery. The optimization requires buffering the results from the subquery, and we require that you modify the query to opt-in the new behavior.

例如:

var query = context.Customers.Select(
    c => c.Orders.Where(o => o.Amount  > 100).Select(o => o.Amount).ToList());

注意包含 .ToList() 的位置。

您需要相应地修改您的投影查询,以启用优化功能。


你的情况可能是:

source.Select(post => new PostProject
            {
                PostDisableCoins = post.PostDisableCoins
                                    .Select(x => x.OrderBy(x=>x.CoinAmount))
                                    .Select(x => x) 
                                    .Take(3)                                  
                                    .ToList(),
                WarStartTime = post.WarStartTime,
                WarEndTime = post.WarEndTime,
                WarWinner = post.WarWinner,
                WarDeclarer = post.WarDeclarer
            }); 

这是 EF Core 2.1 实现缺陷。以下是解决方法,但仅当您确实遇到性能问题时才使用它,因为它需要打破导航 属性 连接抽象并使用手动连接,我总是说不应该与 EF (Core) 一起使用。如果用于投影多个集合或作为更复杂查询的一部分,也可能不起作用。

它需要使用横向连接将集合导航 属性 post.PostDisableCoins 的用法替换为 SelectMany 并隐藏 OrderBy / Take 运算符(更新使用正确的类型和 PK/FK 个名称):

var postDisableCoinsQuery = source.SelectMany(p =>
    db.Set<PostDisableCoin>()
        .Where(c => c.PostId == p.Id)
        .OrderByDescending(c => c.CoinAmount)
        .Take(3)
);

然后对其进行GroupJoin

var query = 
    from p in source
    join c in postDisableCoinsQuery on p.Id equals c.PostId into postDisableCoins
    select new PostProject
    {
        PostDisableCoins = postDisableCoins.ToList(),
        WarStartTime = p.WarStartTime,
        WarEndTime = p.WarEndTime,
        WarWinner = p.WarWinner,
        WarDeclarer = post.WarDeclarer
    };

执行时,以上将通过单个 SQL 查询产生所需的结果。