如何使用实体框架获取记录计数匹配谓词

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.WhenAllCountAsyncToListAsync 包装到并发异步调用中会更好,这将是更注重性能的解决方案 您使用来自 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&lt;T&gt;().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 获取每个组的最新记录,包括相关实体

使用 NSFetchedResultsController 的子实体到父实体的谓词

获取满足谓词的实体,甚至有些实体的父对象为空

如何创建谓词以获取同一属性的所有不重复结果的实体?

coredata 获取关系不存在的记录

如何使用实体关系来获取数组的计数差异