In Entity Framework, we can implement pagination easily using the Skip()
and Take()
methods.
In applications that handle large volumes of data, retrieving all records from a database in a single query may not be feasible because it would take too much time.
For example, if we load 10,000 products from a catalog, it would be very slow. Moreover, the user does not need (nor can they) see 10,000 products at once.
Pagination allows us to split the results into manageable chunks. This enhances the user experience and also reduces resource consumption.
What are Skip
and Take
?
These methods belong to LINQ and are used to:
Skip(n)
: Skips the firstn
elements of a query.Take(m)
: Selects the nextm
elements after skipping.
Together, they allow for very easy pagination.
For example, if we have a Products
table with 100 records and want to display 10 per page, the second page can be obtained with:
var page2 = dbContext.Products
.OrderBy(p => c.Id)
.Skip(10) // Skips the first 10
.Take(10) // Takes the next 10
.ToList();
Basic pagination implementation
Let’s suppose we want to paginate a list of clients in a web application. The code would be:
public List<Client> GetPaginatedClients(int page, int recordsPerPage)
{
using (var context = new AppDbContext())
{
return context.Clients
.OrderBy(c => c.Name) // Important to sort!
.Skip((page - 1) * recordsPerPage)
.Take(recordsPerPage)
.ToList();
}
}
(page - 1) * recordsPerPage
calculates how many records to skip.- If
page = 1
,Skip(0)
returns from the first record.
Calculation of the total number of pages
To implement complete pagination, it is often necessary to calculate the total number of available pages.
This can be done by dividing the total number of records by the page size:
public (List<Client> Data, int TotalPages) GetPaginatedClients(int page, int perPage)
{
using (var context = new AppDbContext())
{
var total = context.Clients.Count();
var totalPages = (int)Math.Ceiling(total / (double)perPage);
var data = context.Clients
.OrderBy(c => c.Name)
.Skip((page - 1) * perPage)
.Take(perPage)
.ToList();
return (data, totalPages);
}
}
This value can be used to display pagination controls in the user interface, such as “Previous” and “Next” buttons.
Avoid Skip
on large offsets
In tables with many (millions) of records, Skip()
can be slow. This is because Skip
has to traverse all previous records before skipping them.
To optimize performance in these cases, techniques such as:
- Indexes on sorting columns: Ensure that the columns used in
OrderBy
are indexed. - Key-based pagination: Instead of using
Skip
, use aWhere
condition based on the last value of the previous page.
For example, if we are paginating products by their ID:
int lastId = 10; // Last ID from the previous page
int pageSize = 10;
var products = context.Products
.Where(p => p.Id > lastId)
.OrderBy(p => p.Id)
.Take(pageSize)
.ToList();