Impaginazione
L'impaginazione si riferisce al recupero dei risultati nelle pagine, anziché contemporaneamente; questa operazione viene in genere eseguita per set di risultati di grandi dimensioni, in cui viene visualizzata un'interfaccia utente che consente all'utente di passare alla pagina successiva o precedente dei risultati.
Avviso
Indipendentemente dal metodo di impaginazione usato, assicurarsi sempre che l'ordine sia completamente univoco. Ad esempio, se i risultati vengono ordinati solo per data, ma possono essere presenti più risultati con la stessa data, i risultati potrebbero essere ignorati durante l'impaginazione mentre vengono ordinati in modo diverso tra due query di impaginazione. L'ordinamento per data e ID (o qualsiasi altra proprietà o combinazione di proprietà univoche) rende l'ordinamento completamente univoco ed evita questo problema. Si noti che i database relazionali non applicano alcun ordinamento per impostazione predefinita, anche nella chiave primaria.
Nota
Azure Cosmos DB ha un proprio meccanismo per l'impaginazione, vedere la pagina della documentazione dedicata.
Impaginazione offset
Un modo comune per implementare la paginazione con i database consiste nell'usare gli operatori Skip
e Take
LINQ (OFFSET
e LIMIT
in SQL). Data una dimensione di pagina di 10 risultati, la terza pagina può essere recuperata con EF Core come indicato di seguito:
var position = 20;
var nextPage = context.Posts
.OrderBy(b => b.PostId)
.Skip(position)
.Take(10)
.ToList();
Purtroppo, sebbene questa tecnica sia molto intuitiva, presenta anche alcune gravi carenze:
- Il database deve comunque elaborare le prime 20 voci, anche se non vengono restituite all'applicazione; In questo modo viene creato un carico di calcolo potenzialmente significativo che aumenta con il numero di righe ignorate.
- Se si verificano aggiornamenti simultaneamente, l'impaginazione potrebbe finire per ignorare determinate voci o visualizzarle due volte. Ad esempio, se una voce viene rimossa man mano che l'utente passa dalla pagina 2 alla 3, l'intero set di risultati "sposta verso l'alto" e una voce viene ignorata.
Impaginazione dei keyset
L'alternativa consigliata all'impaginazione basata su offset, talvolta denominata paginazione keyset o paginazione basata su seek consiste nell'usare semplicemente una clausola WHERE
per ignorare le righe, anziché un offset. Ciò significa ricordare i valori rilevanti dell'ultima voce recuperata (anziché il relativo offset) e chiedere le righe successive dopo tale riga. Si supponga, ad esempio, che l'ultima voce dell'ultima pagina recuperata abbia un valore ID pari a 55, è sufficiente eseguire le operazioni seguenti:
var lastId = 55;
var nextPage = context.Posts
.OrderBy(b => b.PostId)
.Where(b => b.PostId > lastId)
.Take(10)
.ToList();
Supponendo che un indice sia definito in PostId
, questa query è molto efficiente e non è sensibile anche alle modifiche simultanee che si verificano nei valori ID inferiori.
La paginazione keyset è appropriata per le interfacce di paginazione in cui l'utente passa avanti e indietro, ma non supporta l'accesso casuale, in cui l'utente può passare a una pagina specifica. L'impaginazione ad accesso casuale richiede l'uso dell'impaginazione offset come illustrato in precedenza; a causa delle carenze dell'impaginazione offset, valutare attentamente se l'impaginazione ad accesso casuale è effettivamente necessaria per il caso d'uso o se lo spostamento della pagina successiva/precedente è sufficiente. Se è necessaria un'impaginazione ad accesso casuale, un'implementazione affidabile potrebbe usare la paginazione keyset quando si passa alla pagina successiva/precedente e lo spostamento scostamento quando si passa a qualsiasi altra pagina.
Più chiavi di paginazione
Quando si usa la paginazione keyset, è spesso necessario ordinare in base a più proprietà. Ad esempio, la query seguente impagina per 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();
In questo modo, la pagina successiva sceglie esattamente dove è terminata quella precedente. Man mano che vengono aggiunte altre chiavi di ordinamento, è possibile aggiungere clausole aggiuntive.
Nota
La maggior parte dei database SQL supporta una versione più semplice ed efficiente di quanto sopra, usando i valori di riga: WHERE (Date, Id) > (@lastDate, @lastId)
. EF Core attualmente non supporta l'espressione nelle query LINQ, che viene rilevata da #26822.
Indici
Come per qualsiasi altra query, l'indicizzazione corretta è fondamentale per ottenere prestazioni ottimali: assicurarsi di disporre di indici sul posto che corrispondono all'ordinamento della paginazione. Se l'ordinamento viene eseguito in base a più colonne, è possibile definire un indice su tali colonne; si tratta di un indice composito.
Per maggiori informazioni, vedere pagina di documentazione sugli indici.
Risorse aggiuntive
- Per maggiori informazioni sulle carenze della paginazione basata su offset e sull'impaginazione keyset, vedere questo post.
- Sessione di standup della community di dati .NET in cui vengono illustrati i concetti di paginazione e demo di tutti i concetti precedenti.
- Presentazione di approfondimento tecnico che confronta l'offset e la paginazione keyset. Anche se il contenuto riguarda il database PostgreSQL, le informazioni generali sono valide anche per altri database relazionali.
- Per le estensioni su EF Core che semplificano l'impaginazione del keyset, vedere MR. EntityFrameworkCore.KeysetPagination e MR. AspNetCore.Pagination.