Better way to query a page of data and get total count in entity framework 4.1?
Solution 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.
Solution 2
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();
Solution 3
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
Solution 4
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.
C.J.
Updated on July 05, 2022Comments
-
C.J. almost 2 years
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.
-
Jone Polvora over 10 yearsIt will throw an error if the table has no records. To solve it, just replace .First() with .FirstOrDefault() and remember to check if the result is not null.
-
Prageeth godage over 9 yearsI also have same problem because I currently used DAPPER and it has query multiple option to retrieve multiple queries in single call.adrift solution is admirable witch I already think it was not possible in EF. many thanks adrift.
-
Jamie Nordmeyer over 8 yearsBeautifully done! I needed this for a paging grid of data, as I'm sure the other users did, and I would've never thought of this on my own, so thanks so much! You have my up-vote!
-
CodeHacker about 8 yearsBAM! Good one! Just perfect for a simple query like I'm using
-
Michael Freidgeim about 7 yearsCaching the total can cause inconsistency, if number of records changed between the first and subsequent page calls
-
Michael Freidgeim about 7 yearsIt's a clever trick, but to have code maintainable it's better to have simple design with 2 calls
-
Bryan about 7 yearsit can, but often it doesn't matter, especially if there are many ages of results. When I needed the count and results together a single query was too slow and hard to read compared with two queries.
-
xr280xr almost 7 yearsJust make sure your cached total count is cached specifically for any where clause. If your first query is
ctx.People.Where (p => p.Name.StartsWith("A"))
, you don't want to reuse the total count on the next queryctx.People.Where (p => p.Name.StartsWith("B"))
-
Matt Vukomanovic over 6 yearsNote this also doesn't work correctly if you skip past the entire row set It would need to be changed to .FirstOrDefault() or .ToList() instead of .First()
-
Rudey almost 6 yearsThis probably never performs better than just doing two queries. My answer improves a little bit on this, but unfortunately the decreased performance still isn't worth it.
-
Jonathan ANTOINE almost 6 yearswon't i query the count for each item ?
-
Rudey almost 6 yearsNo. The database engine will optimize the query and only perform the count once.
-
SimonGates over 5 years@JonathaANTOINE if you're using EFCore >= 1.1.x then yes it will.
-
Brian McCord about 5 yearsJust tested this on EFCore 2.21. Only produces one query when written as above. However, if instead of specifying each field, you do a Person = p, it will produce a count for each row.
-
MÇT over 4 yearsIt seems that it has been solved at EF Core 3.0.0 according to this: github.com/aspnet/EntityFrameworkCore/issues/…
-
moreginger over 4 yearsI liked the look of this, but even with
FirstOrDefault
it doesn't give you the total if you have records but skip them all (i.e. with a page number that is too high). -
Matt about 4 yearswhat if we're not in Core at all? will this work in EF 6.x?
-
Владимiръ about 4 yearsThis is evil. You should never run parallel operations on the same DB context instance. docs.microsoft.com/en-us/ef/core/querying/async
-
Deepak Shaw almost 4 yearsthe 'totalCount" has a limitation, will give ZERO after row finished, which is incorrect. I have improvised the last a bit
code
var totalCount = results.FirstOrDefault()?.TotalCount ?? await query.CountAsync(); -
Rudey almost 4 years@DeepakShaw good point, I've edited my answer to include your fix.
-
sommmen about 2 yearsHas anyone tried this in .net 6 (ef core 6) because it looks like this will produce 2 queries now.