Linq实现GroupBy分组后每组取20条 禁用AsEnumerable不加载内存
问题说明
你当前的写法在GroupBy()后直接调用Take(20),是对分组集合本身做截取,最终只会返回20个分组,无法实现每个分组下取20条数据的需求。
以下实现可直接被EF Core翻译为原生SQL执行,全程不需要加载全量数据到内存,也无需调用AsEnumerable()做客户端计算。
正确实现代码
_context.Products .Where(predicate) .AsNoTracking() .GroupBy(t => new { CategoryName = t.CategoryName, Id = t.CategoryId }) .Select(g => g .OrderBy(t => Guid.NewGuid()) // 组内随机排序,若需按固定字段排序替换此处即可 .Take(20) .Select(t => new ProductViewModel { foo = bar // 替换为你实际的ViewModel字段映射逻辑 }) ) .SelectMany(res => res) .ToList();
实现说明
- 上述写法会被EF Core翻译为带
PARTITION BY的窗口函数(ROW_NUMBER()分页逻辑),所有计算都在数据库端完成,不会拉取全表数据到内存。 - 原代码中全局的
OrderBy(t => Guid.NewGuid())会在分组操作后失效,因此将随机排序逻辑移动到组内,符合你随机取每组20条的需求;如果需要对最终返回的所有结果做全局排序,可在SelectMany()后追加OrderBy逻辑。 - 原代码将
ProductViewModel映射放在分组之前,会导致分组阶段查询多余字段,将映射移动到组内Take(20)之后,只会查询最终返回的20*分组数量条数据的对应字段,查询性能更高。 - 目前SqlServer、PostgreSQL、Pomelo维护的MySQL等主流EF Core数据库提供程序,都可正常翻译上述语法,EF Core 3.0及以上版本均可支持。
注意:如果使用Oracle等小众数据库提供程序,请先确认对应版本对组内Top N查询的翻译支持,避免出现客户端评估的性能问题。
内容的提问来源于stack exchange,提问作者seme
相关产品推荐
相关产品推荐

