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的主要内容,如果未能解决你的问题,请参考以下文章