32

In Entity Framework, using LINQ to Entities, database paging is usually done in following manner:

int totalRecords = EntityContext.Context.UserSet.Count;
var list     = EntityContext.Context.UserSet
                 .Skip(startingRecordNumber)
                 .Take(pageSize)
                 .ToList();

This results in TWO database calls.

Please tell, how to reduce it to ONE database call.

Thank You.

dev
  • 1,291
  • 3
  • 18
  • 26
  • 7
    In EF it results in error, you have to call OrderBy prior to calling Skip :) Would be good if you update your code. Somebody could have lost a lot of time copying the code from the post. – Arman Oct 02 '14 at 10:55
  • The trick how to do it is in http://stackoverflow.com/questions/7767409/better-way-to-query-a-page-of-data-and-get-total-count-in-entity-framework-4-1, but it's better to have simple design with 2 calls – Michael Freidgeim Mar 10 '17 at 21:39

6 Answers6

41

Whats wrong with two calls? They are small and quick queries. Databases are designed to support lots of small queries.

A developing a complex solution to do one query for paging isn't going give you much pay off.

Casey Burns
  • 1,223
  • 12
  • 15
7

Using Esql and mapping a stored procedure to an entity can solve the problem. SP will return totalRows as output parameter and current page as resultset.

CREATE PROCEDURE getPagedList(
@PageNumber int,
@PageSize int,
@totalRecordCount int OUTPUT
AS

//Return paged records

Please advise.

Thank You.

dev
  • 1,291
  • 3
  • 18
  • 26
  • 1
    +1 Nice. That would give you 1 database call. You'd still be executing two queries, but they'll be quick and small. – Casey Burns Jul 21 '11 at 21:48
5

Hmmm... the actual call that uses paging is the second one - that's a single call.

The second call is to determine the total number of rows - that's quite a different operation, and I am not aware of any way you could combine those two distinct operations into a single database call with the Entity Framework.

Question is: do you really need the total number of rows? What for? Is that worth a second database call or not?

Another option you would have is to use the EntityObjectSource (in ASP.NET) and then bind this to e.g. a GridView, and enable AllowPaging and AllowSorting etc. on the GridView, and let the ASP.NET runtime handle all the nitty-gritty work of retrieving the appropriate data page and displaying it.

Marc

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • 4
    You need the total records so you know how many pages you have in your paged interface. I wonder if you could just do a list.Count or something? – rball Jun 26 '09 at 16:21
  • Well, the List.Count probably would fetch all rows from the database - that's definitely not something you want. Also, I am pretty sure Linq to Entities will return an empty set of you ask for a page beyond it's actual set of data - so again: why do you need the total number of rows? Really? Sure it's nice to show "Page 5 of 17" on your page - can you live without it? – marc_s Jun 26 '09 at 16:27
  • 1
    Thanks for replies. UI requires a pager bar, so total record count is needed. – dev Jun 26 '09 at 16:56
  • If you do paging on a static data, the totalRecords will the same and shouldn't be retrieved on every page change. If data may change between pages, this may confuse a user. – Kamarey Jun 26 '09 at 16:57
  • Thanks. The objective is to reflect current rows (including new additions and deletions). – dev Jul 01 '09 at 22:26
  • @Robert: I see that a lot - but the question remains - is that **really** necessary? Are you willing to pay the price for enumerating over a huge data set just to know how many rows there are in total? Just a question - maybe you do need that - but then you need to be willing to pay the price for that feature - it's always a tradeoff in computing..... – marc_s Feb 02 '10 at 13:59
  • @marc_s User usually wants to know if they are browsing a set of 100 results or 1000000 results. Because in the case of 1M, maybe that are not looking at the right thing. And simply going through it might take an eternity. Example - you are told that you arrived in US, but population of the country you arrived in is only 1 million. Your conclusion - well, it's not US for sure. – Victor Zakharov Nov 24 '22 at 12:04
3
ALTER proc [dbo].[GetNames]
    @lastRow bigint,
    @pageSize bigint,
    @totalRowCount bigint output
as
begin

select @totalRowCount = count(*) from _firstNames, _lastNames

select
    FirstName,
    LastName,
    RowNumber
from
(
    select
        fn.[FirstName] as FirstName,
        ln.[Name] as LastName,
        row_number() over( order by FirstName ) as RowNumber
    from
        _firstNames fn, _lastNames ln
) as data
where
    RowNumber between ( @lastRow + 1 ) and ( @lastRow + @pageSize )

end 

There is no way to get this into one call, but this works fast enough.

Milan Solanki
  • 1,207
  • 4
  • 25
  • 48
0

This queries are too small for DBManager and I can not understand why you want to do this, anyway for reduce it to ONE database call use this:

var list     = EntityContext.Context.UserSet
                 .Skip(startingRecordNumber)
                 .Take(pageSize)
                 .ToList();
int totalRecords = list.Count;
Mahdi Ahmadi
  • 441
  • 4
  • 12
  • 1
    OP wants count of all the records and not the paginated records. In your solution the `totalRecords` would be equal to `pageSize`. – Vinit Divekar Jun 06 '19 at 01:42
-1

Suppose you want to get the details of Page 2 with a pagesize=4

int page =2;
int pagesize=4;

var pagedDetails= Categories.Skip(pagesize*(page-1)).Take(pagesize)
.Join(Categories.Select(item=>new {item.CategoryID,Total = Categories.Count()}),x=>x.CategoryID,y=>y.CategoryID,(x,y)=>new {Category = x,TotalRows=y.Total});

The Output will have all details of Category and TotalRows.

One DB call.

Generated SQL

-- Region Parameters
DECLARE @p0 Int = 2
DECLARE @p1 Int = 4
-- EndRegion
SELECT [t2].[CategoryID], [t2].[CategoryName], [t2].[Description], [t2].[Picture], [t5].[value] AS [TotalRows]
FROM (
    SELECT [t1].[CategoryID], [t1].[CategoryName], [t1].[Description], [t1].[Picture], [t1].[ROW_NUMBER]
    FROM (
        SELECT ROW_NUMBER() OVER (ORDER BY [t0].[CategoryID], [t0].[CategoryName]) AS [ROW_NUMBER], [t0].[CategoryID], [t0].[CategoryName], [t0].[Description], [t0].[Picture]
        FROM [Categories] AS [t0]
        ) AS [t1]
    WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p0 + @p1
    ) AS [t2]
INNER JOIN (
    SELECT [t3].[CategoryID], (
        SELECT COUNT(*)
        FROM [Categories] AS [t4]
        ) AS [value]
    FROM [Categories] AS [t3]
    ) AS [t5] ON [t2].[CategoryID] = [t5].[CategoryID]
ORDER BY [t2].[ROW_NUMBER]
Satchi
  • 477
  • 5
  • 9
  • Wrong. It always counts all categories. Even if a where is applied on the main query. Moreover, it doesn't return Category entities but an anonymous type, and it counts again and again for each category. – Gert Arnold Jun 01 '16 at 11:50
  • The requirement is : 1- Get the details of a Page with a pagesize 2- Make sure you get back all info in one DB CALL. I hope you understand what you are talking...Did you see the requirements above? Have you tried the solution provided by me or you are sharing your thoughts just by looking ? If you try, it will be one DB call...please try and then comment. Of Course the category entity does not have a total count property but we still want it back in the same call... The return type is an anonymous class with concrete properties Category and TotalRows. – Satchi Jun 02 '16 at 02:14
  • One *little* question. What if they want `Categories.Where(c => c.Name.Contains("a"))`? – Gert Arnold Jun 02 '16 at 06:01
  • int page =1; int pagesize=4; var pagedDetails= Categories.Where(c => c.CategoryName.Contains("a")).Skip(pagesize*(page-1)).Take(pagesize) .Join(Categories.Select(item=>new {item.CategoryID,Total = Categories.Count()}) ,x=>x.CategoryID,y=>y.CategoryID,(x,y)=>new {Category = x,TotalRows=y.Total}); This Will work – Satchi Jun 08 '16 at 02:16
  • There you go. The count is meaningless now. – Gert Arnold Jun 08 '16 at 05:38
  • 1
    I thought it will be easy for you to figure that out from the example I have provided...this is the modified query int page =1; int pagesize=3; var pagedDetails= Categories.Where(c => c.CategoryName.Contains("a")) .Skip(pagesize*(page-1)).Take(pagesize) .Join(Categories.Select(item=>new {item.CategoryID,Total = Categories.Where(c => c.CategoryName.Contains("a")).Count()}) ,x=>x.CategoryID,y=>y.CategoryID,(x,y)=>new {Category = x,TotalRows=y.Total}); pagedDetails.Dump(); – Satchi Jun 14 '16 at 00:14
  • and this is just one DB call again...:) – Satchi Jun 14 '16 at 00:15
  • Yeah. But don't you see the point? You always have to repeat the `where` clause, the whole query actually. What if the query gets a little bit more complex with joins, subqueries and all? Really, it doesn't make sense to squeeze it all into one db call. – Gert Arnold Jun 14 '16 at 06:45
  • I hope you understand the intent of the Question. The Question was "Please tell, how to reduce it to ONE database call.?" Its about the possibility Not if its the best way ...therefore I provided answer as per the question...I perfectly know if you do it in one call there is additional complexity and performance impact. But to answer the question, If its possible ? Of Course its possible. I hope you understand now the reason behind my answer. – Satchi Jun 14 '16 at 17:08