Conceito e implementação de paging
ResumoPaginação evita retornar coleções gigantes de uma vez. Implementamos
Skip/Takeno repositório, encapsulamos parâmetros num objeto e expomos metadados de paginação no cabeçalhoX-Pagination.
1. Objetivos de Aprendizagem#
- Justificar a necessidade de paginação.
- Implementar paging com
Skip/Takeno EF Core. - Retornar metadados de paginação de forma padronizada.
2. Pré-requisitos#
- Pilha assíncrona (Módulo 28).
3. Conceito#
O diagrama abaixo resume o fluxo principal.
Sem paginação, GET /produtos pode retornar milhares de registros — lento, caro e arriscado. Paginação fatia o resultado em páginas (pageNumber, pageSize). O porquê: previsibilidade de tamanho de resposta, menor uso de memória e melhor experiência do cliente.
A contagem total permite ao cliente saber quantas páginas existem; expomos isso no cabeçalho X-Pagination.
4. Mão na Massa#
4.1. Setup#
Sem pacotes novos.
4.2. Implementação Passo a Passo#
- Parâmetros com limites:
public class ProdutoParameters
{
private const int MaxPageSize = 50;
public int PageNumber { get; set; } = 1;
private int _pageSize = 10;
public int PageSize
{
get => _pageSize;
set => _pageSize = value > MaxPageSize ? MaxPageSize : value;
}
}
- Lista paginada com metadados:
public class PagedList<T> : List<T>
{
public int CurrentPage { get; }
public int TotalPages { get; }
public int PageSize { get; }
public int TotalCount { get; }
public PagedList(IEnumerable<T> items, int count, int pageNumber, int pageSize)
{
TotalCount = count;
PageSize = pageSize;
CurrentPage = pageNumber;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
AddRange(items);
}
public static async Task<PagedList<T>> CreateAsync(
IQueryable<T> source, int pageNumber, int pageSize, CancellationToken ct)
{
var count = await source.CountAsync(ct);
var items = await source.Skip((pageNumber - 1) * pageSize)
.Take(pageSize).ToListAsync(ct);
return new PagedList<T>(items, count, pageNumber, pageSize);
}
}
- Repositório e controller:
var pagedProdutos = await PagedList<Produto>.CreateAsync(
query, parameters.PageNumber, parameters.PageSize, ct);
Response.Headers["X-Pagination"] = JsonSerializer.Serialize(new
{
pagedProdutos.CurrentPage, pagedProdutos.TotalPages,
pagedProdutos.PageSize, pagedProdutos.TotalCount
});
return Ok(_mapper.Map<IEnumerable<ProdutoDto>>(pagedProdutos));
4.3. Executando#
curl -i "http://localhost:5000/api/produtos?pageNumber=2&pageSize=5"
# resposta inclui o header X-Pagination
5. Exemplo Completo#
O X-Pagination entrega ao cliente tudo que ele precisa para renderizar controles de página sem consultas extras.
6. Boas Práticas e Armadilhas#
| Faça | Evite |
|---|---|
Impor um PageSize máximo | Deixar o cliente pedir 1.000.000 de itens |
| Sempre ordenar antes de paginar | Skip/Take sem OrderBy (ordem não determinística) |
| Expor metadados de paginação | Retornar só os itens sem contexto de total |
Atenção
Skip/TakesemOrderByproduz resultados imprevisíveis — o banco não garante ordem. Sempre ordene por uma chave estável.
7. Segurança e Produção#
- O limite de
PageSizeprotege contra respostas gigantes e ataques de exaustão. - Em tabelas muito grandes, considere keyset pagination (cursor) em vez de
Skip, que degrada em offsets altos.
8. Exercícios#
- Fácil: ajuste o
MaxPageSizee valide o efeito. - Médio: retorne
400quandopageNumber < 1. - Desafio: implemente keyset pagination usando o último id como cursor.
9. Resumo#
Paginação fatia coleções com Skip/Take, sempre com ordenação e limite de tamanho, expondo metadados via X-Pagination. Para grandes volumes, keyset pagination escala melhor.
10. Próximos Passos#
Módulo 31: filtragem dos resultados por critérios.
11. Referências#
- Microsoft Learn — Paginação eficiente no EF Core
- Microsoft Learn — Skip e Take
- Microsoft Learn — Cabeçalhos de resposta customizados