如何使用实体框架获取记录计数匹配谓词
Posted
技术标签:
【中文标题】如何使用实体框架获取记录计数匹配谓词【英文标题】:How I can get record count matching predicate using Entity Framework 【发布时间】:2018-05-21 11:44:44 【问题描述】:如何在下面的函数中获取与谓词匹配的记录数?需要生成分页。
public async virtual Task<IEnumerable<T>> GetAll(Expression<Func<T, bool>> predicate, int pageNo, int pageSize)
return (await dbContext.Set<T>().Where(predicate).Skip(pageSize * (pageNo - 1)).Take(pageSize).ToListAsync());
【问题讨论】:
【参考方案1】:要计算项目的数量需要在分页之前使用 Count() 方法完成。 这里是 Microsoft 文档中的示例:
public static async Task<PaginatedList<T>> CreateAsync(IQueryable<T> source, int pageIndex, int pageSize)
var count = await source.CountAsync();
var items = await source.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
更多详情请查看以下链接: https://docs.microsoft.com/en-us/aspnet/core/data/ef-mvc/sort-filter-page?view=aspnetcore-2.0
【讨论】:
使用await Task.WhenAll
将CountAsync
和ToListAsync
包装到并发异步调用中会更好,这将是更注重性能的解决方案
您使用来自 EF 扩展的异步调用的解决方案是一个好主意 +1 解决方案【参考方案2】:
您应该在 ToList() 调用之前单独查询并使用单独的 Count() 调用。 相关部分:
var query = dbContext.Set<T>().Where(predicate);
var cnt = query.Count();
var result = query.Skip(pageSize * (pageNo - 1)).Take(pageSize).ToList();
【讨论】:
唯一的问题是OP需要Count
在下面的部分dbContext.Set<T>().Where(predicate)
,否则除了最后一页外,该值固定为PageSize
,可能会更少
这个查询将只返回不正确的页面大小计数,就像@MrinalKamboj 提到的那样。
对。已更新。【参考方案3】:
到目前为止,所有答案都是在两个查询中完成的。我认为这很糟糕,并且由于 SQL 优化的奇迹,您可以做一些小技巧来避免需要对分页计数进行第二次查询。
不要对计数进行第二次查询,而是将计数附加到每一行,然后在结果集中将其分开。
下面是我写的一个小帮手来帮助做到这一点:
public static IQueryable<EntityWithCount<T>> GetWithTotal<T>(this IQueryable<T> entities, int page, int pageSize) where T : class
return entities
.Select(e => new EntityWithCount<T> Entity = e, Count = entities.Count() )
.Skip((page-1) * pageSize)
.Take(pageSize);
public class EntityWithCount<T> where T : class
public T Entity get; set;
public int Count get; set;
Full source on github
对于合理的页面大小,此方法速度更快,并且避免了您在执行多个查询时可能遇到的任何事务问题。
您可以将其链接在任何其他未枚举查询的末尾(例如问题中的查询,替换跳过/采取和.ToListAsync()
调用之前)
【讨论】:
EF 会将调用转换为Select * , count(*) from ... where ...
,这比进行两个异步调用一个执行Select *
,另一个执行Select Count(*)
的性能要好得多,因为应用程序可以提供更快的结果,事实上在您的解决方案中,它会导致每行有额外的 count(*) 列,这会浪费内存,随着行数的增加,它会变得庞大
@MrinalKamboj 仅为此查询生成了一个查询计划和服务器往返。 SQL 会将每行的 count(*) 优化为对整个查询的一次调用。这意味着您基本上用往返+查询计划换取数据传输中每行的额外整数。这对于任何合理的页面大小都可以忽略不计。它还具有交易完整性的重要好处。在您的示例中,您可能会返回 9 行,计数为 8(例如,如果有人在查询的同时删除了某些内容)
查询计划肯定会针对 count(*) 进行优化,仍然会获取额外的列/行,这与行数成正比是无法避免的,虽然可能不会对内存产生巨大影响但会产生多余的结果集。关于事务不会有任何区别,结果提供了默认的ReadCommitted模式,在Sql查询的情况下它是隐式事务,对于Async请求事务是绑定到IDbConnection的,会有确切的影响,这都是关于在相同的事务上下文中登记。 (续:)
(续:)当今的大多数系统都更喜欢并发异步请求,特别是因为数据库能够处理百万+事务/秒。使用Task.WhenAll
执行异步请求更加高效和灵活,即使在这种情况下,Select 查询将使用与其执行相同查询相同的查询计划 -“Select ... using same predicate”
@MrinalKamboj read committed 不能保证查询之间的结果一致,见***.com/a/4036063/1070291,至于双查询更快,你需要测试一下,但我的猜测是会有难以察觉的差异在低延迟、多核系统上,在高延迟/负载系统中,差异会更加明显。例如,如果他们坐在优先队列中,您将等待最后一个查询,即使您本可以在第一个查询中得到结果以上是关于如何使用实体框架获取记录计数匹配谓词的主要内容,如果未能解决你的问题,请参考以下文章
如何使用实体框架和 MySQL 获取每个组的最新记录,包括相关实体