如何在Entity Framework GroupBy中选择每个组的前N行,使用EF 3.1

22

我需要使用实体框架从表中获取每个分组的前10行数据。根据Stack Overflow上的其他解决方案,我尝试了以下两种方法:

var sendDocuments = await context.Set<DbDocument>
    .Where(t => partnerIds.Contains(t.SenderId))
    .GroupBy(t => t.SenderId)
    .Select(t => new
    {
        t.Key,
        Documents = t.OrderByDescending(t2 => t2.InsertedDateTime).Take(10)
    })                
    .ToArrayAsync();

错误:

System.InvalidOperationException: 'The LINQ expression
'(GroupByShaperExpression: KeySelector: (d.SenderId), 
ElementSelector:(EntityShaperExpression: 
    EntityType: DbDocument
    ValueBufferExpression: 
        (ProjectionBindingExpression: EmptyProjectionMember)
    IsNullable: False ) )
    .OrderByDescending(t2 => t2.InsertedDateTime)' 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
> either AsEnumerable(), AsAsyncEnumerable(), ToList(), or
> ToListAsync().

并且

var sendDocuments2 = await context.Set<DbDocument>
    .Where(t => partnerIds.Contains(t.SenderId))
    .GroupBy(t => t.SenderId)
    .SelectMany(t => t.OrderByDescending(t2 => t2.InsertedDateTime).Take(10))
    .ToArrayAsync();

错误:

System.InvalidOperationException:“NavigationExpandingExpressionVisitor”无法处理LINQ表达式“t => t.OrderByDescending(t2 => t2.InsertedDateTime).AsQueryable().Take(10)”。这可能表示EF Core中存在错误或限制。

还有其他想法吗?


你能提供一下你想用 SQL 查询做什么吗?或者你能提供一下表结构吗? - Asım Gündüz
context.Set<DbDocument>.FromSqlRaw 并编写其中的 group by 查询。 - divyang4481
1个回答

36

更新(EF Core 6.0):

EF Core 6.0增加了对于转换GroupBy结果集投影的支持,因此原始代码用于获取(键、项)现在可以正常工作,也就是说:

var query = context.Set<DbDocument>()
    .Where(e => partnerIds.Contains(e.SenderId))
    .GroupBy(e => e.SenderId)
    .Select(g => new
    {
        g.Key,
        Documents = g.OrderByDescending(e => e.InsertedDateTime).Take(10)
    });

然而,通过SelectMany进行平坦化仍然不受支持,因此如果需要这样的查询形状,则必须使用下面的解决方法。
原始代码(EF Core 3.0/3.1/5.0):
这是一个常见的问题,不幸的是EF Core 3.0/3.1/5.0查询转换器特别针对GroupBy不支持。
解决方法是通过关联两个子查询手动执行分组-一个用于键,另一个用于相应的数据。
将其应用于您的示例将如下所示。
如果需要(key, items)对:
var query = context.Set<DbDocument>()
    .Where(t => partnerIds.Contains(t.SenderId))
    .Select(t => t.SenderId).Distinct() // <--
    .Select(key => new
    {
        Key = key,
        Documents = 
            context.Set<DbDocument>().Where(t => t.SenderId == key) // <--
                 .OrderByDescending(t => t.InsertedDateTime).Take(10)
                 .ToList() // <--
    });

如果您只需要一个扁平的结果集,其中包含每个键的前N个项目:
var query = context.Set<DbDocument>()
    .Where(t => partnerIds.Contains(t.SenderId))
    .Select(t => t.SenderId).Distinct() // <--
    .SelectMany(key => context.Set<DbDocument>().Where(t => t.SenderId == key) // <--
        .OrderByDescending(t => t.InsertedDateTime).Take(10)
    );

1
当存在多个键时,第一个查询不起作用。它总是获取一个键。 - Murat Can OĞUZHAN
3
这帮助了我很多,但我无法解释。在执行 GroupBy(x => x.key) 时,请将其翻译为 Select(x => x.key).Distinct().SelectMany(key => <your_table>.Where(t => t.key == key))。 - Carlos M. Hernández

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接