在实体框架 4.1 中查询一页数据并获取总计数的更好方法?

发布于 2024-12-10 08:23:58 字数 474 浏览 0 评论 0原文

目前,当我需要运行一个将与分页一起使用的查询时,我会这样做:

//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();

这有效,但我想知道是否可以在仍然使用 linq 的同时改进它以提高效率?我想不出一种方法可以将计数与数据检索结合到一次数据库访问中,而无需使用存储过程。

Currently when I need to run a query that will be used w/ paging I do it something like this:

//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();

This works, but I wondered if it is possible to improve this to be more efficient while still using linq? I couldn't think of a way to combine the count w/ the data retrieval in a single trip to the DB w/o using a stored proc.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

油焖大侠 2024-12-17 08:23:58

以下查询将在一次数据库访问中获取计数和页结果,但如果您检查 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);

对于像这样的简单查询,您可能可以使用任一方法(2 次访问数据库,或使用 GroupBy 在 1 次行程中完成),并且不会注意到太大的差异。对于任何复杂的事情,我认为存储过程将是最好的解决方案。

The following query will get the count and page results in one trip to the database, but if you check the SQL in LINQPad, you'll see that it's not very pretty. I can only imagine what it would look like for a more complex query.

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);

For a simple query like this, you could probably use either method (2 trips to the database, or using GroupBy to do it in 1 trip) and not notice much difference. For anything complex, I think a stored procedure would be the best solution.

疑心病 2024-12-17 08:23:58

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();

这几乎做了同样的事情,只是它不会用不必要的 GROUP BY 来打扰数据库。当您不确定查询将至少包含一个结果,并且不希望它抛出异常时,您可以通过以下方式(尽管不太干净)获取totalCount

var totalCount = results.FirstOrDefault()?.TotalCount ?? query.Count();

:变体:

var totalCount = await results.FirstOrDefaultAsync()?.TotalCount ?? await query.CountAsync();

Jeff Ogata's answer can be optimized a little bit.

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();

This does pretty much the same thing except it won't bother the database with an unnecessary GROUP BY. When you are not certain your query will contain at least one result, and don't want it to ever throw an exception, you can get totalCount in the following (albeit less cleaner) way:

var totalCount = results.FirstOrDefault()?.TotalCount ?? query.Count();

And the async variant:

var totalCount = await results.FirstOrDefaultAsync()?.TotalCount ?? await query.CountAsync();
谁把谁当真 2024-12-17 08:23:58

对于使用 EF Core >= 1.1.x && 的用户的重要说明< 3.0.0:

当时我正在寻找此问题的解决方案,并且此页面在 Google 术语“EF Core Paging Total Count”中排名第一。

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

这是使用 EF Core 2.1.4 和 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             
};

EF Core 团队似乎已经意识到这一点:

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

Important Note for People using EF Core >= 1.1.x && < 3.0.0:

At the time I was looking for solution to this and this page is/was Rank 1 for the google term "EF Core Paging Total Count".

Having checked the SQL profiler I have found EF generates a SELECT COUNT(*) for every row that is returned. I have tired every solution provided on this page.

This was tested using EF Core 2.1.4 & SQL Server 2014. In the end I had to perform them as two separate queries like so. Which, for me at least, isn't the end of the world.

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             
};

It looks like the EF Core team are aware of this:

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

满栀 2024-12-17 08:23:58

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

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

I suggest making two queries for the first page, one for the total count and one for the first page or results.

Cache the total count for use as you move beyond the first page.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文