在 EF Core 中选择分组项

Selecting Grouped Items in EF Core

这在 EF 6.4 中有效:

from a in Addresses
group a by new {a.StreetName, a.StreetNumber} into agrp
where agrp.Count() > 3
from aitem in agrp
select aitem

如果 EF Core 5 我得到:

InvalidOperationException: The LINQ expression 'agrp => agrp' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

为什么?有没有不同的写法?

如果您同意将数据加载到内存中,一个简单的解决方案可能是在 Addresses:

之后添加 .ToList().AsEnumerable()
from a in Addresses.ToList() // or .AsEnumerable()
group a by new {a.StreetName, a.StreetNumber} into agrp
where agrp.Count() > 3
from aitem in agrp
select aitem

请注意,这(在 SqlServer 中)转换为:

SELECT [a].[Id], [a].[StreetName], [a].[StreetNumber]
FROM [Addresses] AS [a]

在 EF Core 中,GroupBy(在许多情况下)未转换为 SQL,但在内存中为 运行。 (为避免不小心将大量数据加载到内存中,EF 将抛出异常,除非调用 .ToList().AsEnumerable() 以表明这是故意的。)

(...) Since no database structure can represent an IGrouping, GroupBy operators have no translation in most cases. When an aggregate operator is applied to each group, which returns a scalar, it can be translated to SQL GROUP BY in relational databases. (...)

- Complex query operators, GroupBy

该文章还有一个查询示例,该查询转换为 group by,并在 Count 上进行筛选(包括在下方)。 不幸的是,该示例并未完全涵盖问题中的示例。它不会 return 相关的 Address-objects,只有 group-by Key 和 Count。

var query = from p in context.Set<Post>()
            group p by p.AuthorId into g
            where g.Count() > 0
            orderby g.Key
            select new
            {
                g.Key,
                Count = g.Count()
            };
SELECT [p].[AuthorId] AS [Key], COUNT(*) AS [Count]
FROM [Posts] AS [p]
GROUP BY [p].[AuthorId]
HAVING COUNT(*) > 0
ORDER BY [p].[AuthorId]

这似乎有效:

var addressGroupQuery = from a in Addresses
                        group a by new {a.StreetName, a.StreetType} into agrp
                        where agrp.Count() > 3
                        select agrp.Max(a => a.AddressID);
                
var addresses = from a in Addresses
                where addressGroupQuery.Contains(a.AddressID)
                select a;

请注意,addressGroupQuery 仍然是一个延迟查询(没有 ToList)。也生成一些干净的外观 SQL:

SELECT [a].[AddressID], [a].[City], [a].[Country], [a].[StreetName], [a].[StreetNumber], [a].[StreetType], [a].[UnitNumber]
FROM [Address] AS [a]
WHERE EXISTS (
    SELECT 1
    FROM [Address] AS [a0]
    GROUP BY [a0].[StreetName], [a0].[StreetType]
    HAVING (COUNT(*) > 3) AND (MAX([a0].[AddressID]) = [a].[AddressID]))