Пагинация в 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