如何使用EF Core在单查询中同时获取分页数据与总条数?
解决EF Core中单查询同时获取分页数据与总结果数的问题
你当前的LINQ写法会触发两次查询,原因是Select中调用的Count()会被EF Core解析为独立的聚合查询,而非窗口函数。要实现和你手动编写的MySQL语句一致的单查询效果,可通过以下两种方式解决:
方法一:使用EF Core内置的窗口函数支持(推荐)
EF Core 5.0及以上版本支持通过EF.Functions.CountOver()映射SQL的窗口函数,直接生成包含COUNT(*) OVER ()的单查询:
// 构建筛选查询 var filterQuery = dbContext.Users.Where(u => u.Name.Contains("John")); // 投影包含窗口函数计算的总条数,再执行分页 var queryResult = filterQuery .Select(u => new { User = u, TotalCount = EF.Functions.CountOver() }) .OrderBy(item => item.User.Name) .Skip(0) .Take(30) .ToList(); // 提取总条数(所有结果的TotalCount值相同,取第一个即可) int totalCount = queryResult.FirstOrDefault()?.TotalCount ?? 0; // 提取当前页的数据 var pageData = queryResult.Select(item => item.User).ToList();
此写法会生成与你手动编写一致的SQL:
SELECT u.*, COUNT(*) OVER () AS `TotalCount` FROM `Users` AS `u` WHERE `u`.`Name` LIKE '%John%' ORDER BY `u`.`Name` LIMIT 30 OFFSET 0
方法二:直接执行原生SQL(兼容低版本EF Core)
如果你的EF Core版本低于5.0,可通过FromSqlRaw直接执行手动编写的MySQL语句,需要注意接收结果的实体或DTO需包含TotalCount字段:
int pageSize = 30; int offset = 0; string searchKeyword = "%John%"; // 假设存在包含TotalCount的DTO类:UserWithTotalCount var result = dbContext.Set<UserWithTotalCount>().FromSqlRaw(@" SELECT u.*, COUNT(*) OVER () AS TotalCount FROM Users u WHERE u.Name LIKE {0} ORDER BY u.Name LIMIT {1}, {2}", searchKeyword, offset, pageSize) .ToList(); int totalCount = result.FirstOrDefault()?.TotalCount ?? 0; var pageData = result.Select(item => new User { Id = item.Id, Name = item.Name // 映射其他字段 }).ToList();
关键说明
- 避免在
Select中直接调用Count():这种写法会让EF Core优先执行聚合查询获取总条数,再执行分页查询,导致两次数据库交互。 - 窗口函数的优势:单查询即可同时获取分页数据和总条数,减少数据库往返次数,提升性能。
内容的提问来源于stack exchange,提问作者Mensur
相关产品推荐
相关产品推荐

