Partilhar via


Paginação

Paginação refere-se à recuperação de resultados em páginas, em vez de todos de uma só vez; Isso geralmente é feito para grandes conjuntos de resultados, onde uma interface do usuário é mostrada que permite que o usuário navegue até a página seguinte ou anterior dos resultados.

Advertência

Independentemente do método de paginação utilizado, certifique-se sempre de que a sua encomenda é totalmente única. Por exemplo, se os resultados estiverem ordenados apenas por data, mas podem existir vários resultados com a mesma data, então os resultados podem ser ignorados na paginação, pois estão ordenados de forma diferente em duas consultas de paginação. Ordenar por data e ID (ou qualquer outra propriedade única ou combinação de propriedades) torna a ordem totalmente única e evita este problema. Note que as bases de dados relacionais não aplicam qualquer ordenação por defeito, mesmo na chave primária.

Observação

O Azure Cosmos DB tem o seu próprio mecanismo de paginação, consulte a página dedicada à documentação.

Paginação offset

Uma maneira comum de implementar paginação com bancos de dados é usar os Skip operadores e Take LINQ (OFFSET e LIMIT em SQL). Dado um tamanho de página de 10 resultados, a terceira página pode ser obtida com o EF Core da seguinte forma:

var position = 20;
var nextPage = await context.Posts
    .OrderBy(b => b.PostId)
    .Skip(position)
    .Take(10)
    .ToListAsync();

Infelizmente, embora esta técnica seja muito intuitiva, também apresenta algumas falhas graves:

  1. A base de dados ainda tem de processar as primeiras 20 entradas, mesmo que não sejam devolvidas à aplicação; isto cria uma carga computacional possivelmente significativa que aumenta com o número de linhas a serem saltadas.
  2. Se houver atualizações em simultâneo, a sua paginação pode acabar por saltar certas entradas ou mostrá-las duas vezes. Por exemplo, se uma entrada for removida enquanto o utilizador passa da página 2 para a 3, todo o conjunto de resultados "sube" e uma entrada será ignorada.

Paginação por keyset

A alternativa recomendada à paginação baseada em deslocamento – por vezes chamada de paginação por conjunto de chaves ou paginação baseada em busca – é usar simplesmente uma cláusula WHERE SQL para saltar linhas, em vez de um deslocamento. Isto significa recordar os valores relevantes da última entrada obtida (em vez do seu deslocamento) e solicitar as linhas seguintes após essa linha. Por exemplo, assumindo que a última entrada na última página que recuperámos tinha o valor de ID 55, faríamos simplesmente o seguinte:

var lastId = 55;
var nextPage = await context.Posts
    .OrderBy(b => b.PostId)
    .Where(b => b.PostId > lastId)
    .Take(10)
    .ToListAsync();

Assumindo que um índice está definido em PostId, esta consulta é muito eficiente e também não é sensível a quaisquer alterações simultâneas que ocorram em valores de Id mais baixos.

A paginação por keyset é adequada para interfaces de paginação onde o utilizador navega para a frente e para trás, mas não suporta acesso aleatório, podendo saltar para qualquer página específica. A paginação de acesso aleatório requer o uso de paginação deslocada, como explicado acima; Devido às limitações da paginação deslocada, considere cuidadosamente se a paginação de acesso aleatório é realmente necessária para o seu caso de uso, ou se a navegação na página seguinte/anterior é suficiente. Se for necessária paginação de acesso aleatório, uma implementação robusta pode usar paginação por conjunto de chaves ao navegar para a página seguinte/anterior e navegação por deslocamento ao saltar para qualquer outra página.

Múltiplas chaves de paginação

Ao usar paginação por keyset, torna-se frequentemente necessário ordenar por mais do que uma propriedade. Por exemplo, a consulta seguinte pagina-se por data e ID:

var lastDate = new DateTime(2020, 1, 1);
var lastId = 55;
var nextPage = await context.Posts
    .OrderBy(b => b.Date)
    .ThenBy(b => b.PostId)
    .Where(b => b.Date > lastDate || (b.Date == lastDate && b.PostId > lastId))
    .Take(10)
    .ToListAsync();

Isto garante que a página seguinte começa exatamente onde a anterior terminou. À medida que mais chaves de ordenação são adicionadas, podem ser adicionadas cláusulas adicionais.

Observação

A maioria das bases de dados SQL suporta uma versão mais simples e eficiente do acima, usando valores de linha: WHERE (Date, Id) > (@lastDate, @lastId). O EF Core atualmente não suporta expressar isto em consultas LINQ, isto é monitorizado pelo #26822.

Indexes

Como em qualquer outra consulta, uma indexação adequada é vital para um bom desempenho: certifique-se de que existem índices que correspondam à sua ordem de paginação. Se ordenar por mais do que uma coluna, pode ser definido um índice sobre essas múltiplas colunas; Isto chama-se índice composto.

Para mais informações, consulte a página de documentação sobre índices.

Recursos adicionais