Aniket.EntityFrameworkCore.Pagination 1.0.0

dotnet add package Aniket.EntityFrameworkCore.Pagination --version 1.0.0
                    
NuGet\Install-Package Aniket.EntityFrameworkCore.Pagination -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Aniket.EntityFrameworkCore.Pagination" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Aniket.EntityFrameworkCore.Pagination" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Aniket.EntityFrameworkCore.Pagination" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Aniket.EntityFrameworkCore.Pagination --version 1.0.0
                    
#r "nuget: Aniket.EntityFrameworkCore.Pagination, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Aniket.EntityFrameworkCore.Pagination@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Aniket.EntityFrameworkCore.Pagination&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Aniket.EntityFrameworkCore.Pagination&version=1.0.0
                    
Install as a Cake Tool

Aniket.EntityFrameworkCore.Pagination

A lightweight and reusable pagination library for ASP.NET Core applications using Entity Framework Core.

The library provides a simple ToPagedListAsync() extension method for IQueryable<T> that handles pagination, total record calculation, total page calculation, and pagination parameter validation.

Features

  • Generic pagination using IQueryable<T>
  • Works with Entity Framework Core
  • Asynchronous database operations
  • Calculates total number of records
  • Calculates total number of pages
  • Returns current page and page size
  • Validates pagination parameters
  • Configurable through simple extension methods
  • Works with any entity type
  • Keeps pagination logic out of controllers and services

Requirements

  • .NET 10
  • Entity Framework Core 10

Installation

Install the package using the .NET CLI:

dotnet add package Aniket.EntityFrameworkCore.Pagination

Or add the package reference manually to your .csproj:

<PackageReference Include="Aniket.EntityFrameworkCore.Pagination" Version="1.0.0" />

Basic Usage

Assume you have an Entity Framework Core DbContext:

public class AppDbContext : DbContext
{
    public DbSet<Employee> Employees { get; set; }

    public AppDbContext(
        DbContextOptions<AppDbContext> options)
        : base(options)
    {
    }
}

Import the pagination extension:

using Aniket.EntityFrameworkCore.Pagination.Extensions;

You can then paginate an Entity Framework Core query:

var result = await _context.Employees
    .ToPagedListAsync(page, pageSize);

For example:

var result = await _context.Employees
    .ToPagedListAsync(2, 10);

This requests:

  • Page 2
  • 10 records per page

Filtering Before Pagination

Because the extension method works with IQueryable<T>, you can compose your query before applying pagination.

var result = await _context.Employees
    .Where(e => e.Salary > 50000)
    .ToPagedListAsync(2, 10);

The filtering is applied before pagination.

You can also use ordering:

var result = await _context.Employees
    .Where(e => e.Salary > 50000)
    .OrderBy(e => e.Name)
    .ToPagedListAsync(2, 10);

How It Works

The pagination flow is:

IQueryable<T>
     |
     | CountAsync()
     ↓
Total Items
     |
     | Skip()
     | Take()
     ↓
Current Page Items
     |
     | ToListAsync()
     ↓
PagedResult<T>

For example:

var result = await _context.Employees
    .ToPagedListAsync(2, 10);

Internally, the library performs the equivalent of:

var totalItems = await query.CountAsync();

var items = await query
    .Skip((page - 1) * pageSize)
    .Take(pageSize)
    .ToListAsync();

It then calculates the total number of pages and returns a PagedResult<T>.

Response Structure

The method returns:

PagedResult<T>

The result contains:

Property Description
Items Records belonging to the requested page
Page Current page number
PageSize Maximum number of records requested per page
TotalItems Total number of matching records
TotalPages Total number of available pages

Example:

{
  "items": [
    {
      "id": 11,
      "name": "Employee 11"
    },
    {
      "id": 12,
      "name": "Employee 12"
    }
  ],
  "page": 2,
  "pageSize": 10,
  "totalItems": 125,
  "totalPages": 13
}

The example above shows only two items for brevity. A page with pageSize = 10 can contain up to 10 items.

Generic Support

PagedResult<T> is generic, so the library can be used with any entity type.

For employees:

PagedResult<Employee>

For products:

PagedResult<Product>

For orders:

PagedResult<Order>

For example:

var products = await _context.Products
    .ToPagedListAsync(1, 20);

And:

var orders = await _context.Orders
    .ToPagedListAsync(3, 25);

Pagination Rules

The library validates the supplied pagination parameters.

Page

The page number must be greater than or equal to 1.

Valid:

page = 1
page = 2
page = 10

Invalid:

page = 0
page = -1

An invalid page number throws:

ArgumentOutOfRangeException

Page Size

The page size must be greater than or equal to 1.

Valid:

pageSize = 1
pageSize = 10
pageSize = 100

Invalid:

pageSize = 0
pageSize = -1

An invalid page size throws:

ArgumentOutOfRangeException

Maximum Page Size

The current default maximum page size is:

100

Therefore:

pageSize = 100

is valid, while:

pageSize = 101

throws:

ArgumentOutOfRangeException

This prevents clients from accidentally requesting extremely large result sets.

Empty Results

If the query doesn't contain any matching records, the library returns an empty Items collection.

For example:

{
  "items": [],
  "page": 1,
  "pageSize": 10,
  "totalItems": 0,
  "totalPages": 0
}

Last Page

The last page may contain fewer records than the requested page size.

For example, if there are:

125 total records
pageSize = 10

there are:

13 total pages

The pages contain:

Page 1  → 1–10
Page 2  → 11–20
...
Page 12 → 111–120
Page 13 → 121–125

Therefore, pageSize represents the maximum number of records per page, not a guarantee that every page contains exactly that number.

Why IQueryable<T>?

The extension method accepts:

IQueryable<T>

rather than:

List<T>

This allows Entity Framework Core to compose the query and execute pagination at the database level.

For example:

var result = await _context.Employees
    .Where(e => e.Salary > 50000)
    .OrderBy(e => e.Name)
    .ToPagedListAsync(2, 10);

The filtering, ordering, skipping, and taking can be translated by Entity Framework Core into a database query.

This avoids loading the entire table into application memory before applying pagination.

Complete Controller Example

using Aniket.EntityFrameworkCore.Pagination.Extensions;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class EmployeesController : ControllerBase
{
    private readonly AppDbContext _context;

    public EmployeesController(AppDbContext context)
    {
        _context = context;
    }

    [HttpGet]
    public async Task<IActionResult> GetEmployees(
        int page = 1,
        int pageSize = 10)
    {
        var result = await _context.Employees
            .OrderBy(e => e.Id)
            .ToPagedListAsync(page, pageSize);

        return Ok(result);
    }
}

A request such as:

GET /api/employees?page=2&pageSize=10

returns the second page of employees.

Error Handling

The library validates pagination arguments and throws standard .NET exceptions when invalid values are supplied.

Example:

await _context.Employees
    .ToPagedListAsync(0, 10);

throws:

ArgumentOutOfRangeException

Applications can handle these exceptions using their existing ASP.NET Core exception-handling or validation middleware.

Testing

The library is tested using xUnit and Entity Framework Core with SQLite.

The test suite covers:

  • Successful pagination
  • Correct page number
  • Correct page size
  • Correct number of returned items
  • Correct total item count
  • Correct total page count
  • Invalid page numbers
  • Invalid page sizes
  • Maximum page size validation

Run the tests with:

dotnet test

Building the Package

To build the project:

dotnet build

To create the NuGet package:

dotnet pack

The generated package will be placed under:

bin/Release/

Example Pagination

Given 125 employees:

Page size = 10

The result is:

Page 1  → Employees 1–10
Page 2  → Employees 11–20
Page 3  → Employees 21–30
...
Page 12 → Employees 111–120
Page 13 → Employees 121–125

The metadata for page 2 is:

{
  "page": 2,
  "pageSize": 10,
  "totalItems": 125,
  "totalPages": 13
}

Package Structure

Aniket.EntityFrameworkCore.Pagination
│
├── Extensions
│   └── QueryableExtensions.cs
│
├── Models
│   └── PagedResult.cs
│
├── README.md
└── Aniket.EntityFrameworkCore.Pagination.csproj

Design Goals

The library is designed to:

  • Keep pagination logic reusable
  • Reduce repeated Skip() and Take() code
  • Keep pagination logic out of controllers
  • Work with generic entity types
  • Execute pagination at the database level
  • Provide consistent pagination metadata
  • Keep the API simple

License

This project is licensed under the MIT License.

See the LICENSE file for details.

Author

Aniket Madaan

Version

Current version:

1.0.0
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 110 8/25/2026