Paginação
Paginação refere-se à recuperação de resultados em páginas, em vez de tudo de uma vez. Isso normalmente é feito para conjuntos de resultados grandes, em que uma interface do usuário é mostrada que permite que o usuário navegue até a próxima página ou página anterior dos resultados.
Aviso
Independentemente do método de paginação usado, certifique-se sempre de que sua ordenação seja totalmente exclusiva. Por exemplo, se os resultados forem ordenados apenas por data, mas puderem haver vários resultados com a mesma data, os resultados poderão ser ignorados ao paginar, pois são ordenados de forma diferente em duas consultas paginando. A ordenação por data e ID (ou qualquer outra propriedade individual ou combinação de propriedades) torna a ordenação totalmente exclusiva e evita esse problema. Observe que os bancos de dados relacionais não aplicam nenhuma ordenação por padrão, mesmo na chave primária.
Observação
O Azure Cosmos DB tem seu próprio mecanismo de paginação, consulte a página de documentação dedicada.
Paginação de deslocamento
Uma maneira comum de implementar a paginação com bancos de dados é usar os operadores Skip
e Take
LINQ (OFFSET
e LIMIT
no SQL). Considerando um tamanho de página de 10 resultados, a terceira página pode ser buscada com o EF Core da seguinte maneira:
var position = 20;
var nextPage = context.Posts
.OrderBy(b => b.PostId)
.Skip(position)
.Take(10)
.ToList();
Infelizmente, embora essa técnica seja muito intuitiva, ela também tem algumas deficiências graves:
- O banco de dados ainda deve processar as primeiras 20 entradas, mesmo que elas não sejam retornadas ao aplicativo; isso cria uma carga de computação possivelmente significativa que aumenta com o número de linhas sendo ignoradas.
- Se ocorrer alguma atualização simultaneamente, sua paginação poderá acabar ignorando determinadas entradas ou mostrando-as duas vezes. Por exemplo, se uma entrada for removida à medida que o usuário estiver movendo da página 2 para 3, todo o conjunto de resultados "será movido para cima" e uma entrada será ignorada.
Paginação de conjunto de chaves
A alternativa recomendada à paginação baseada em deslocamento, às vezes chamada de paginação de conjunto de chaves ou paginação baseada em busca, é simplesmente usar uma cláusula WHERE
para ignorar linhas, em vez de um deslocamento. Isso significa lembrar os valores relevantes da última entrada buscada (em vez de seu deslocamento) e solicitar as próximas linhas após essa linha. Por exemplo, supondo que a última entrada na última página que buscamos tivesse um valor de ID de 55, simplesmente faríamos o seguinte:
var lastId = 55;
var nextPage = context.Posts
.OrderBy(b => b.PostId)
.Where(b => b.PostId > lastId)
.Take(10)
.ToList();
Supondo que um índice seja definido no PostId
, essa consulta é muito eficiente e também não é sensível a alterações simultâneas que ocorrem em valores de ID mais baixos.
A paginação de conjunto de chaves é apropriada para interfaces de paginação em que o usuário navega para frente e para trás, mas não dá suporte a acesso aleatório, em que o usuário pode ir para qualquer página específica. A paginação de acesso aleatório requer o uso da paginação de deslocamento, conforme explicado acima. Devido às deficiências da paginação de deslocamento, considere cuidadosamente se a paginação de acesso aleatório realmente é necessária para seu caso de uso ou se a navegação de página próxima/anterior é suficiente. Se a paginação de acesso aleatório for necessária, uma implementação robusta poderá usar a paginação do conjunto de chaves ao navegar para a página seguinte/anterior e deslocar a navegação ao saltar para qualquer outra página.
Várias chaves de paginação
Ao usar a paginação do conjunto de chaves, é frequentemente necessário solicitar por mais de uma propriedade. Por exemplo, a consulta a seguir pagina por data e ID:
var lastDate = new DateTime(2020, 1, 1);
var lastId = 55;
var nextPage = context.Posts
.OrderBy(b => b.Date)
.ThenBy(b => b.PostId)
.Where(b => b.Date > lastDate || (b.Date == lastDate && b.PostId > lastId))
.Take(10)
.ToList();
Isso garante que a próxima página escolha exatamente onde a anterior terminou. À medida que mais chaves de ordenação são adicionadas, cláusulas adicionais podem ser adicionadas.
Observação
A maioria dos bancos de dados SQL dá suporte a uma versão mais simples e eficiente do acima, usando valores de linha: WHERE (Date, Id) > (@lastDate, @lastId)
. Atualmente, o EF Core não dá suporte para expressar isso em consultas LINQ, isso é acompanhado pelo #26822.
Índices
Assim como acontece com qualquer outra consulta, a indexação adequada é vital para um bom desempenho: certifique-se de ter índices em vigor que correspondam à sua ordenação de paginação. Se a ordenação por mais de uma coluna, um índice sobre essas várias colunas poderá ser definido. Isso é chamado de índice composto.
Para obter mais informações, consulte a página de documentação sobre índices.
Recursos adicionais
- Para saber mais sobre as deficiências da paginação baseada em deslocamento e sobre a paginação do conjunto de chaves, consulte essa postagem.
- Sessão Standup da Comunidade de Dados do .NET em que discutimos a paginação e a demonstração de todos os conceitos acima.
- Uma apresentação técnica de aprofundamento comparando a paginação de deslocamento e conjunto de chaves. Embora o conteúdo lide com o banco de dados PostgreSQL, as informações gerais também são válidas para outros bancos de dados relacionais.
- Para extensões sobre o EF Core que simplificam a paginação do conjunto de chaves, consulte MR.EntityFrameworkCore.KeysetPagination e MR.AspNetCore.Pagination.