更好的方法来查询数据页面并获得实体框架4.1中的总计数?

C.J*_*.J. 56 entity-framework entity-framework-4 c#-4.0 entity-framework-4.1

目前,当我需要运行将使用w/paging的查询时,我会这样做:

//Setup query (Typically much more complex)
var q = ctx.People.Where(p=>p.Name.StartsWith("A"));

//Get total result count prior to sorting
int total = q.Count();       

//Apply sort to query
q = q.OrderBy(p => p.Name);  

q.Select(p => new PersonResult
{
   Name = p.Name
}.Skip(skipRows).Take(pageSize).ToArray();
Run Code Online (Sandbox Code Playgroud)

这有效,但我想知道是否有可能在使用linq时提高效率更高效?我想不出一种方法可以使用存储过程将数据检索的计数与数据检索结合到数据库中.

Jef*_*ata 77

以下查询将在一次数据库中获取计数和页面结果,但如果在LINQPad中检查SQL,则会发现它不是很漂亮.我只能想象一下更复杂的查询会是什么样子.

var query = ctx.People.Where (p => p.Name.StartsWith("A"));

var page = query.OrderBy (p => p.Name)
                .Select (p => new PersonResult { Name = p.Name } )          
                .Skip(skipRows).Take(pageSize)
                .GroupBy (p => new { Total = query.Count() })
                .First();

int total = page.Key.Total;
var people = page.Select(p => p);
Run Code Online (Sandbox Code Playgroud)

对于像这样的简单查询,你可以使用任何一种方法(2次访问数据库,或者GroupBy在1次旅行中使用它)并没有注意到很多不同.对于任何复杂的事情,我认为存储过程将是最好的解决方案.

  • 如果表没有记录,它将抛出错误.要解决它,只需用.FirstOrDefault()替换.First()并记住检查结果是否为null. (17认同)
  • +1,很好!我根本没想到它是可能的. (4认同)
  • 这可能永远不会比只执行两个查询更好。我的答案对此有所改进,但不幸的是,性能下降仍然不值得。 (4认同)
  • 这是一个聪明的技巧,但为了让代码可维护,最好通过 2 次调用进行简单的设计 (3认同)

Rud*_*dey 8

Jeff Ogata的答案可以稍微优化一下.

var results = query.OrderBy(p => p.Name)
                   .Select(p => new
                   {
                       Person = new PersonResult { Name = p.Name },
                       TotalCount = query.Count()
                   })          
                   .Skip(skipRows).Take(pageSize)
                   .ToArray(); // query is executed once, here

var totalCount = results.First().TotalCount;
var people = results.Select(r => r.Person).ToArray();
Run Code Online (Sandbox Code Playgroud)

这几乎完全相同,除了它不会打扰带有不必要的GROUP BY的数据库.当您不确定您的查询将包含至少一个结果,并且不希望它抛出异常时,您可以获得totalCount以下(虽然不太清晰)的方式:

var totalCount = results.FirstOrDefault()?.TotalCount ?? 0;
Run Code Online (Sandbox Code Playgroud)

  • 刚刚在 EFCore 2.21 上对此进行了测试。如上所述编写时仅生成一个查询。但是,如果不是指定每个字段,而是执行 Person = p,它将为每一行生成一个计数。 (3认同)
  • 有没有人在 .net 6 (ef core 6) 中尝试过这个,因为看起来这现在会产生 2 个查询。 (2认同)

Sim*_*tes 6

使用EF Core> = 1.1.x && <3.0.0的人员的重要说明:

当时我正在寻找解决方案,此页面在Google术语“ EF核心分页总数”中排名第1。

检查完SQL事件探查器后,我发现EF SELECT COUNT(*)为返回的每一行生成一个。我已经厌倦了此页面上提供的所有解决方案。

这是使用EF Core 2.1.4和SQL Server 2014进行了测试。最后,我不得不像两个单独的查询一样执行它们。至少对我来说,这不是世界末日。

var query = _db.Foo.AsQueryable(); // Add Where Filters Here.


var resultsTask = query.OrderBy(p => p.ID).Skip(request.Offset).Take(request.Limit).ToArrayAsync();
var countTask = query.CountAsync();

await Task.WhenAll(resultsTask, countTask);

return new Result()
{
    TotalCount = await countTask,
    Data = await resultsTask,
    Limit = request.Limit,
    Offset = request.Offset             
};
Run Code Online (Sandbox Code Playgroud)

看起来EF核心团队已经意识到了这一点:

https://github.com/aspnet/EntityFrameworkCore/issues/13739 https://github.com/aspnet/EntityFrameworkCore/issues/11186

  • 这是邪恶的。您永远不应该在同一个数据库上下文实例上运行并行操作。https://learn.microsoft.com/en-us/ef/core/querying/async (6认同)

Bry*_*yan 5

我建议对第一页进行两次查询,一次查询总数,一次查询第一页或结果。

缓存总计数,以便在您超出第一页时使用。

  • 如果第一次和后续页面调用之间的记录数发生变化,缓存总数可能会导致不一致 (7认同)
  • 只需确保您的缓存总数是专门针对任何 where 子句进行缓存的。如果您的第一个查询是 `ctx.People.Where (p =&gt; p.Name.StartsWith("A"))`,您不希望在下一个查询 `ctx.People.Where (p =&gt; p.Name.StartsWith("B"))` (2认同)