Quando precisamos lidar com um grande volume de dados a paginação se torna essencial, principalmente quando esses dados precisam ser apresentados ao usuário de forma eficiente e escalável. Sem uma implementação adequada de paginação, os sistemas podem enfrentar sérios problemas de desempenho e de experiência do usuário, especialmente ao carregar listas de milhares ou milhões de registros.
A paginação é fundamental para:
Otimizar o desempenho: carregar um grande volume de dados de uma vez pode causar lentidão na aplicação e sobrecarga do servidor. A paginação permite carregar apenas uma parte dos dados por vez, o que melhora o desempenho e reduz a latência.
Melhorar a experiência do usuário: a navegação por páginas proporciona uma interação mais fluida, sem necessidade de esperar por um grande conjunto de dados para ser carregado. Os usuários podem acessar os dados de forma mais rápida e sem esperar muito tempo.
Reduzir o uso de recursos: ao carregar somente uma parte dos dados de cada vez, o uso de memória e processamento é significativamente reduzido, o que pode ser crítico em ambientes com muitos usuários simultâneos.
Neste artigo vamos ver três abordagens principais para implementar a paginação em uma aplicação .NET: paginação em banco de dados, paginação com Entity Framework e paginação manual em listas.
A primeira abordagem de paginação é realizada diretamente no banco de dados. Utilizando comandos SQL, é possível carregar uma parte dos dados com base em parâmetros como “OFFSET” e “FETCH NEXT”, que permitem dividir os dados em páginas e evitar a sobrecarga de memória.
public async Task GetPagedProductsAsync(int pageNumber, int pageSize)
{
var sql = @"
SELECT *
FROM Products
ORDER BY ProductID
OFFSET @Offset ROWS
FETCH NEXT @PageSize ROWS ONLY;
";
var offset = (pageNumber - 1) * pageSize;
var products = await _dbConnection.QueryAsync(sql,
new {
Offset = offset,
PageSize = pageSize
});
}
Aqui, o parâmetro “@PageNumber” representa o número da página que você deseja consultar e “@PageSize” representa o número de registros que serão retornados por página. A cláusula “OFFSET” é usada para determinar quantos registros serão ignorados antes de começar a retornar os resultados da consulta, com base no número da página e no tamanho da página.
Usado um exemplo repassando a query com o Dapper, se você receber o “pageNumber = 1” e o “pageSize = 20”, a consulta trará os primeiros 20 registros. Para “pageNumber = 2”, a consulta irá pular os primeiros 20 registros e trará os 20 seguintes, ou seja, do 21º ao 40º registro. A cláusula “FETCH NEXT” é responsável por limitar o número de registros que serão retornados na consulta, baseando-se em “@PageSize”.
Quando utilizamos o Entity Framework (EF) para implementar paginação, a abordagem é mais abstrata e orientada a objetos em comparação com a paginação enviando diretamente o SQL para o banco de dados. Em vez de usar comandos SQL como “OFFSET” e “FETCH NEXT”, o EF fornece os métodos “Skip()” e “Take()” para realizar a paginação de maneira mais simples e intuitiva.
public async Task GetPagedProductsAsync()
{
var pageNumber = 1;
var pageSize = 15;
var query = _context.Products.AsQueryable();
var items = await query
.Skip((pageNumber - 1) * pageSize )
.Take(pageSize)
.ToListAsync();
return items;
}
No exemplo apresentado, a consulta começa criando um “IQueryable” a partir da coleção de produtos (_context.Products), permitindo que você construa e manipule a consulta de maneira eficiente, sem realizar a execução imediata no banco de dados.
O método “Skip()” é utilizado para ignorar os registros anteriores à página solicitada, ou seja, ele “pula” os registros de páginas anteriores. A fórmula usada para calcular o número de registros a serem ignorados é “(pageNumber – 1) * pageSize”, onde “pageNumber” é o número da página e “pageSize” é o número de registros por página.
Já o método “Take()” limita a quantidade de registros retornados para o número de itens da página atual (definido por “pageSize”).
Quando os dados já estão carregados em memória, pode ser necessário implementar a paginação manual em listas, sem o uso de banco de dados. Isso ocorre frequentemente quando você tem uma coleção de dados já disponível, como uma lista em memória, e deseja dividi-la em páginas para melhorar a apresentação ou a navegação.
Esse método é útil quando os dados não são tão grandes e podem ser manipulados diretamente na memória, evitando a necessidade de consultas adicionais ao banco de dados.
public Products GetPagedProducts(List products, int pageNumber, int pageSize)
{
var totalItems = products.Count;
var items = products.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToList();
var totalPages = (int)Math.Ceiling((double)totalItems / pageSize);
return new Products
{
Items = items,
CurrentPage = pageNumber
};
}
No exemplo fornecido, a função “GetPagedProducts” recebe uma lista de produtos (List<Product> products), o número da página (pageNumber) e o tamanho da página (pageSize). Primeiro, o total de itens na lista é calculado usando o método “Count()”. Vamos usar esse valor para calcular a quantidade de páginas mais à frente.
Em seguida, “Skip()” é usado para pular os itens das páginas anteriores, calculando a quantidade de itens a serem ignorados com base no número da página e no tamanho da página: “(pageNumber – 1) * pageSize”. Após isso, o método “Take()” é utilizado para retornar os itens correspondentes à página atual, ou seja, os registros que serão exibidos naquela página.
O número total de páginas é calculado dividindo o total de itens (totalItems) pela quantidade de itens por página (pageSize) e arredondando para cima com “Math.Ceiling()”, para garantir que uma página extra seja retornada quando o total de registros não for divisível perfeitamente pelo tamanho da página.
Por fim, o método retorna uma lista de “Product”, que contém os itens da página atual, e o número da página atual + 1 para que na próxima requisição seja enviada a próxima página. Isso proporciona uma estrutura organizada para a paginação.
Agora vamos demonstrar como criar uma API paginada utilizando uma das abordagens discutidas.
O primeiro passo é criar um controlador simples que recebe parâmetros de query string para paginação (pageSize e pageSize). Caso não seja repassado nenhum valor, os valores padrões serão utilizados.
[Route("api/products")]
[ApiController]
public class ProductsController : ControllerBase
{
private readonly IProductRepository _productRepository;
public ProductsController(IProductRepository productRepository)
{
_productRepository = productRepository;
}
[HttpGet]
public async Task GetPagedProducts(int pageNumber = 1, int pageSize = 10)
{
var result = await _productRepository.GetPagedProductsAsync(pageNumber, pageSize);
return Ok(result);
}
}
Agora vamos criar o “ProductRepository” que encapsula a lógica de obtenção de dados paginados.
using Microsoft.EntityFrameworkCore;
public interface IProductRepository
{
Task GetPagedProductsAsync(int pageNumber, int pageSize);
}
public class ProductRepository : IProductRepository
{
private readonly ApplicationDbContext _context;
public ProductRepository(ApplicationDbContext context)
{
_context = context;
}
public async Task GetPagedProductsAsync(int pageNumber, int pageSize)
{
var query = _context.Products.AsQueryable();
var totalItems = await query.CountAsync();
var items = await query
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
var totalPages = (int)Math.Ceiling((double)totalItems / pageSize);
return new Products
{
Items = items,
CurrentPage = pageNumber +1
};
}
}
Para armazenar os resultados, o modelo de resposta padrão pode ser algo como:
public class Products
{
public List Items { get; set; }
public int CurrentPage { get; set; }
}
Também lembre-se de adicionar o serviço no “Program”:
builder.Services.AddScoped();
Com isso, a paginação da nossa API já está implementada.
Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET Start: Clique aqui
Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET Expert: Clique aqui
Existem várias abordagens para implementar a paginação em aplicações .NET, e cada uma delas tem suas vantagens e desvantagens. Cada técnica tem seu uso ideal, e a escolha da abordagem certa depende das necessidades específicas do seu projeto, como volume de dados, requisitos de desempenho e complexidade.