Skip to main content
.NET Tips

What Is Keyset Pagination?

Keyset Pagination is also known as cursor pagination and is currently one of the fastest pagination techniques in .NET πŸ”₯

β€” Christian Schou

Have you ever tried Keyset Pagination? It's (at the time of writing) one of the fastest pagination techniques when writing .NET code πŸ”₯

In this .NET tip I will show you a quick demo on how you can implement keyset pagination and how fast it is compared to offset pagination.

Why Is It Faster? πŸš€

When doing traditional pagination we fetch records from the database using an offset and limit for the query.

With Keyset Pagination we are using a sortable property on the query. This could be a sequential ID or date time property, from the previous/latest record in our previous dataset/or what some refer to as page.

By using that technique we can make sure that each page returned from the database, begins exactly where our last page ended.

What Are The Use Cases? πŸ€—

By using Keyset Pagination you ensure that your application is able to scale when your datasets do.

It is especially great for making applications with the infinite scroll feature.

If you are making let's say an eCommerce site where people should be able to skip to a specific page using on-site pagination, it would recommend you to stick to the offset pagination logic.

How To Do It πŸ§‘β€πŸ’»

Let's see how to do keyset pagination in .NET.

var books = _dbContext.Books
    .Where(b => b.Id > latestId)
    .Take(pageSize)
    .OrderBy(b => b.Id)
    .Select(b => new BookResponseModel(b.Id, b.Name, b.Author))
    .ToList();

var booksResponse = new KeysetResponse<List<BookResponseModel>>(
    books[^1].Id,
    books);

return booksResponse;

That's how simple you can implement and use Keyset Pagination! Looking for an explanation? πŸ€”

1. Retrieving Books from the Database

var books = _dbContext.Books
    .Where(b => b.Id > latestId)
    .Take(pageSize)
    .OrderBy(b => b.Id)
    .Select(b => new BookResponseModel(b.Id, b.Name, b.Author))
    .ToList();
  • _dbContext.Books - Accesses the Books table from the database context (_dbContext).
  • .Where(b => b.Id > latestId) - Now we apply a filter to filters the books to include only those with an Id greater than our latestId. This is a keyset pagination technique, which is efficient for large datasets.
  • .Take(pageSize) - This will limit the number of books retrieved to pageSize.
  • .OrderBy(b => b.Id) - Sorting the books by their Id in ascending order.
  • .Select(b => new BookResponseModel(b.Id, b.Name, b.Author)) - (I personally love this ❀️) This will project each book entity into a new BookResponseModel object containing Id, Name, and Author properties.
  • .ToList() - Finally converts the result to a list of BookResponseModel objects.

2. Creating the Response

var booksResponse = new KeysetResponse<List<BookResponseModel>>(
    books[^1].Id,
    books);
  • books[^1].Id - This will Get the Id of the last book in the retrieved list (books[^1] uses the C# index from end operator to access the last element, like I explained before).
  • new KeysetResponse<List<BookResponseModel>>(books[^1].Id, books) - Creates a new KeysetResponse object with the Id of the last book and the list of books.

Finally we return the response from our query. The BookResponseModel and KeysetResponse objects are custom classes I made. This is what it looks like.

public class KeysetResponse<T>
{
    public int LastId { get; }
    public T Data { get; }

    public KeysetResponse(int lastId, T data)
    {
        LastId = lastId;
        Data = data;
    }
}


public class BookResponseModel
{
    public int Id { get; }
    public string Name { get; }
    public string Author { get; }

    public BookResponseModel(int id, string name, string author)
    {
        Id = id;
        Name = name;
        Author = author;
    }
}

Conclusion

Keyset pagination is an efficient method for paginating through large datasets, especially when users are expected to navigate sequentially through the data in your application, either forwards or backwards.

This method works by using a reference point, such as the Id of the last item from the previous page, to fetch the next set of records. Here’s why it great ✨

  • Performance - Keyset pagination is optimized for performance. Since it doesn’t need to scan through skipped rows, it can handle large datasets smoothly without significant slowdowns πŸš€
  • Consistency - By using a consistent key (like Id), it ensures that records are not missed or duplicated as users navigate through the pages πŸ”

If you learned something from this .NET Tip, please share it with your friends and developer fellows ✌️

Resources

Pagination - EF Core
Writing paginating queries in Entity Framework Core
Christian Schou