TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.74K subscribers
Post #3260 1.59K
День 2723. #TipsAndTricks
Пагинация в EF Core
Если вы повторяете одну и ту же логику пагинации — Count, Skip и Take — во всём приложении, вы добавляете ненужный код. Рассмотрим более чистый способ.

Проблема
Рассмотрим класс:
public class PagedProductsDto
{
public IEnumerable<ProductDto> Records { get; set; } = [];
public int TotalCount { get; set; }
public int Page { get; set; }
public int PageSize { get; set; }
public int PageCount { get; set; }
}

В самом классе нет ничего плохого. Он содержит все необходимые свойства для пагинации: записи, общее количество, страница, размер страницы и количество страниц. Проблема в том, что он тесно связан с товарами. Если бы мы хотели добавить аналогичный класс, например, для пользователей, нам пришлось бы создать новый класс.

Создадим универсальный класс PagedResults
Он будет принимать в качестве обобщённого параметра тип возвращаемой записи:
public class PagedResults<T>
{
public IEnumerable<T> Records { get; }
public int TotalCount { get; }
public int Page { get; }
public int PageSize { get; }
public int PageCount { get; }
}

В Records мы используем IEnumerable, чтобы обозначить его как доступный только для чтения.

Свойства имеют только аксессор, т.е. доступны только для чтения. Поэтому единственный способ установить их — через конструктор, где мы вычислим все необходимые значения:
public class PagedResults<T>
{
// …
public PagedResults(
IEnumerable<T> records,
int totalCount,
int page,
int pageSize
)
{
Records = records;
TotalCount = totalCount;
Page = page;
PageSize = pageSize;
PageCount = totalCount > 0 && pageSize > 0 ?
(int)Math.Ceiling((double)totalCount / pageSize) : 1;
}
}


Используем в EF
Создадим метод расширения для IQueryable, который позволит применять пагинацию к любым нашим запросам:
public static async Task<PagedResults<T>>
ToPagedResultsAsync<T>(
this IQueryable<T> query,
int page,
int pageSize,
CancellationToken ct = default)
{
var total = await query.CountAsync(ct);

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

return new PagedResults<T>(records, total, page, pageSize);
}

Теперь методы получения результатов короткие, и это применимо к любым сущностям:
public Task<PagedResults<ProductDto>> 
GetPagedProductsAsync(
int page,
int pageSize,
CancellationToken ct = default)
{
return _dbContext.Products
.Include(x => x.Category)
.AsNoTracking()
.Select(x => new ProductDto
{
Id = x.Id,
Name = x.Name,
// …
})
.ToPagedResultsAsync(page, pageSize, ct);
}


Источник:
https://www.roundthecode.com/dotnet-tutorials/pagedresults-ef-core-one-class-endless-reuse
  • 👍 4
More from @netdeveloperdiary
  1. Sep 26, 2026День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три…
  2. Sep 25, 2026День 2795. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Начало Проблема с позиц…
  3. Sep 24, 2026День 2794. #Оффтоп #Здоровье Сегодня будет необычный пост. Завтра в Москве стартует конфер…
  4. Sep 23, 2026День 2793. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  5. Sep 22, 2026День 2792. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  6. Sep 21, 2026🔍Тестовое собеседование с Senior C# разработчиком уже завтра 22 сентября(уже завтра!) в 1…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →