EF Core Linq-to-Sql GroupBy SelectMany 不适用于 SQL Server

Posted

技术标签:

【中文标题】EF Core Linq-to-Sql GroupBy SelectMany 不适用于 SQL Server【英文标题】:EF Core Linq-to-Sql GroupBy SelectMany not working with SQL Server 【发布时间】:2021-11-08 09:35:08 【问题描述】:

我正在尝试使用 LinqPad 使用 EF Core 连接到 SQL Server 的以下 Linq:

MyTable.GroupBy(x => x.SomeField)
       .OrderBy(x => x.Key)
       .Take(5)
       .SelectMany(x => x)

我收到此错误:

无法翻译 LINQ 表达式“x => x”。要么以可翻译的形式重写查询,要么切换到客户端评估显式......

但是,这是可行的:

MyTable.AsEnumerable()
       .GroupBy(x => x.SomeField)
       .OrderBy(x => x.Key)
       .Take(5)
       .SelectMany(x => x)

我的印象是 EF Core 应该能够翻译这样的表达式。

我做错了吗?

【问题讨论】:

【参考方案1】:

该异常消息是 EF Core 消息,而不是 EF6。

在 EF 6 中,您的表达式应该可以工作,尽管末尾带有 ToList() 之类的东西。我怀疑您遇到的错误是您可能在实现集合之前尝试做更多事情,这与SelectMany 评估的组冲突。

例如,像这样的 EF 可能会例外:

var results = MyTable
    .GroupBy(x => x.SomeField)
    .OrderBy(x => x.Key)
    .Take(5)
    .SelectMany(x => x)
    .Select(x => new ViewModel  Id = x.Id, Name = x.Name )
    .ToList();

这样的事情应该在哪里起作用:

var results = MyTable
    .GroupBy(x => x.SomeField)
    .OrderBy(x => x.Key)
    .Take(5)
    .SelectMany(x => x.Select(y => new ViewModel  Id = y.Id, Name = y.Name ))
    .ToList();

你不想使用:

MyTable.AsEnumerable(). ...

由于这是将整个表具体化到内存中,如果保证表保持相对较小,这可能没问题,但如果生产系统显着增长,它会随着时间的推移形成级联性能下降。

编辑:做了一些挖掘,感谢这篇文章,因为它看起来确实是 EF Core 解析器中的另一个限制。 (不知道如何在 EF6 中工作的东西无法成功集成到 EF Core 中......我猜是在重新发明***)

这应该可行:

var results = MyTable
    .GroupBy(x => x.SomeField)
    .OrderBy(x => x.Key)
    .Take(5)
    .Select(x => x.Key)
    .SelectMany(x => _context.MyTable.Where(y => y.Key == x))
    .ToList();

例如,我有一个 Parent 和 Child 表,我想按 ParentId 分组,选取前 5 个父母并选择他们所有的孩子:

var results = context.Children
    .GroupBy(x => x.ParentId)
    .OrderBy(x => x.Key) // ParentId
    .Take(5)
    .Select(x => x.Key) // Select the top 5 parent ID
    .SelectMany(x => context.Children.Where(c => c.ParentId == x)).ToList();

EF 通过在 DbSet 上针对选定的组 ID 执行 SelectMany 将其重新组合在一起。

感谢此处的讨论:How to select top N rows for each group in a Entity Framework GroupBy with EF 3.1

编辑 2:我看这个越多,感觉就越 hacky。另一种选择是将其分解为两个更简单的查询:

var keys = MyTable.OrderBy(x => x.SomeField)
    .Select(x => x.SomeField)
    .Take(5)
    .ToList();

var results = MyTable.Where(x => keys.Contains(x.SomeField))
    .ToList();

我认为这可以翻译您的原始示例,但要点是首先选择适用的 ID/区分键,然后使用这些键查询所需的数据。因此,对于我的前 5 个有孩子的父母的所有孩子:

 var parentIds = context.Children
      .Select(x => x.ParentId)
      .OrderBy(x => x)
      .Take(5)
      .ToList();
 var children = context.Children
    .Where(x => parentIds.Contains(x.ParentId))
    .ToList();

【讨论】:

是的,你是对的 - EF Core .. 这是否意味着这个查询无论如何都不会翻译? (是的,我不想要 .AsEnumerable() 我只是用它来显示我的语法还可以) 你能发布完整的查询吗? (即直到您调用 ToList() 或同等级别。 这是我的完整查询 添加了一个更新,其中包含一些应该可以工作的内容,以及一个指向进一步讨论 EF Core 限制的链接。 嗯,这可能需要在 .Select(x => x.Key) 之后注入 Distinct() 如果查询没有完全尊重 Group By.. 总而言之,这感觉就像一个巨大的黑客工作围绕一些 EF6 能够解决的问题。 :)【参考方案2】:

EF Core 对此类查询有限制,这在 EF Core 6 中已修复。这是 SQL 限制,对于此类 GroupBy 没有直接转换为 SQL。

翻译此 GroupBy 时,EF Core 6 正在创建以下查询。

var results = var results = _context.MyTable
    .Select(x => new  x.SomeField )
    .Distinct()
    .OrderBy(x => x.SomeField)
    .Take(5)
    .SelectMany(x => _context.MyTable.Where(y => y.SomeField == x.SomeField))
    .ToList();

这不是此类任务的最佳查询,因为在 SQL 中,它可以通过窗口函数ROW_NUMBER()PARTITION on SomeField 来表示,并且可以省略附加的JOIN

还要检查this function,它会自动进行此类查询。

_context.MyTable.TakeDistinct(5, x => x.SomeField);

【讨论】:

以上是关于EF Core Linq-to-Sql GroupBy SelectMany 不适用于 SQL Server的主要内容,如果未能解决你的问题,请参考以下文章

EF Core group by 和 order by

Linq-to-sql EF4 - 从生成列表

Linq-to-SQL 数据检索速度比较

EF Core 快速上手——EF Core 入门

EF Core的安装EF Core与数据库结合

为啥使用 EF / Linq to sql 创建性能不佳的查询如此容易[关闭]