Paginierung
Paginierung bedeutet, dass die Ergebnisse seitenweise abgerufen werden und nicht alle auf einmal. Dies geschieht in der Regel bei großen Ergebnismengen, bei denen eine Benutzeroberfläche angezeigt wird, über die der oder die Benutzer*in zur nächsten oder vorherigen Seite der Ergebnisse navigieren kann.
Warnung
Stellen Sie unabhängig von der verwendeten Paginierungsmethode immer sicher, dass Ihre Sortierung wirklich eindeutig ist. Wenn z. B. Ergebnisse nur nach Datum sortiert werden, aber es mehrere Ergebnisse mit demselben Datum geben kann, könnten die Ergebnisse beim Paginieren übersprungen werden, da sie in zwei Paginierungsabfragen unterschiedlich angeordnet sind. Durch Anordnung nach Datum und ID (oder einer anderen eindeutigen Eigenschaft oder Kombination von Eigenschaften) ist die Reihenfolge eindeutig, wodurch dieses Problem vermieden wird. Beachten Sie, dass in relationalen Datenbanken standardmäßig keine Anordnung gilt, auch nicht für den Primärschlüssel.
Hinweis
Azure Cosmos DB verfügt über einen eigenen Mechanismus für die Paginierung, siehe die dedizierte Dokumentationsseite.
Offsetpaginierung
Eine gängige Methode zum Implementieren der Paginierung mit Datenbanken ist die Verwendung der Skip
und Take
LINQ-Operatoren (OFFSET
und LIMIT
in SQL). Bei einer Seitengröße von zehn Ergebnissen kann die dritte Seite wie folgt mit EF Core abgerufen werden:
var position = 20;
var nextPage = context.Posts
.OrderBy(b => b.PostId)
.Skip(position)
.Take(10)
.ToList();
Obwohl diese Methode sehr intuitiv ist, hat sie auch einige schwerwiegende Nachteile:
- Die Datenbank muss weiterhin die ersten 20 Einträge verarbeiten, auch wenn sie nicht an die Anwendung zurückgegeben werden. Dies erzeugt möglicherweise eine erhebliche Rechenlast, die mit der Anzahl der übersprungenen Zeilen zunimmt.
- Wenn Änderungen gleichzeitig auftreten, kann die Paginierung bestimmte Einträge überspringen oder zweimal anzeigen. Wenn beispielsweise ein Eintrag entfernt wird, wenn der oder die Benutzer*in von Seite 2 zu Seite 3 wechselt, wird das gesamte Resultset „nach oben“ verschoben, und ein Eintrag würde übersprungen werden.
Keysetpaginierung
Die empfohlene Alternative zur offsetbasierten Paginierung , die manchmal Keysetpaginierung oder suchbasierte Paginierung genannt wird, besteht darin, eine WHERE
-Klausel zum Überspringen von Zeilen anstelle eines Offsets zu verwenden. Das bedeutet, dass die relevanten Werte aus dem letzten abgerufenen Eintrag (anstelle des Offsets) gespeichert werden und die nächsten Zeilen nach dieser Zeile angefordert werden sollen. Wenn der letzte Eintrag auf der letzten Seite, den Sie abgerufen haben, beispielsweise einen ID-Wert von 55 hatte, führen Sie einfach die folgenden Schritte aus:
var lastId = 55;
var nextPage = context.Posts
.OrderBy(b => b.PostId)
.Where(b => b.PostId > lastId)
.Take(10)
.ToList();
Wenn ein Index für PostId
definiert ist, ist diese Abfrage sehr effizient und wird nicht durch gleichzeitige Änderungen beeinträchtigt, die in niedrigeren ID-Werten auftreten.
Die Keysetpaginierung eignet sich für Paginierungsschnittstellen, bei denen Benutzer*innen vorwärts und rückwärts navigieren, aber unterstützt keinen wahlfreien Zugriff, wo Benutzer*innen zu einer bestimmten Seite springen können. Für die Paginierung mit wahlfreiem Zugriff ist die Verwendung der Offsetpaginierung erforderlich, wie oben erläutert. Aufgrund der Nachteile der Offsetpaginierung sollten Sie sorgfältig prüfen, ob die Paginierung mit wahlfreiem Zugriff wirklich für Ihren Anwendungsfall erforderlich ist oder ob die Navigation zur nächsten/vorherigen Seite ausreichend ist. Wenn die Paginierung mit wahlfreiem Zugriff erforderlich ist, kann eine robuste Implementierung die Keysetpaginierung verwenden, wenn die Navigation zur nächsten/vorherigen Seite erfolgt, und die Offsetnavigation, wenn zu einer anderen Seite gewechselt wird.
Mehrere Paginierungsschlüssel
Bei Verwendung der Keysetpaginierung ist es häufig erforderlich, nach mehreren Eigenschaften zu sortieren. Die folgende Abfrage paginiert z. B. nach Datum und 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();
Dadurch wird sichergestellt, dass die nächste Seite genau an der Stelle weitergeht, an der die vorherige Seite aufgehört hat. Wenn weitere Sortierschlüssel hinzugefügt werden, können zusätzliche Klauseln hinzugefügt werden.
Hinweis
Die meisten SQL-Datenbanken unterstützen eine einfachere und effizientere Version, bei der Zeilenwerte verwendet werden: WHERE (Date, Id) > (@lastDate, @lastId)
. EF Core unterstützt derzeit keine Ausdrücke in LINQ-Abfragen. Dies wird in #26822 nachverfolgt.
Indizes
Wie bei jeder anderen Abfrage ist die ordnungsgemäße Indizierung für eine gute Leistung von entscheidender Bedeutung. Stellen Sie sicher, dass Indizes vorhanden sind, die Ihrer Paginierungsreihenfolge entsprechen. Wenn nach mehr als einer Spalte sortiert wird, kann ein Index für diese Spalten definiert werden. Ein solcher Index wird als zusammengesetzter Index bezeichnet.
Weitere Informationen finden Sie in der Dokumentation zur Paginierung.
Zusätzliche Ressourcen
- Weitere Informationen zu den Nachteilen der offsetbasierten Paginierung und zur Keysetpaginierung finden Sie in diesem Beitrag.
- In der Session .NET Data Community Standup wird die Paginierung behandelt, und die oben genannten Konzepte werden vorgeführt.
- In der technischen Deep-Dive-Präsentation werden die Offset- und Keysetpaginierung verglichen. Obwohl sich der Inhalt auf die PostgreSQL-Datenbank bezieht, gelten die allgemeinen Informationen auch für andere relationale Datenbanken.
- Erweiterungen für EF Core, die die Keysetpaginierung vereinfachen, finden Sie unter MR.EntityFrameworkCore.KeysetPagination und MR.AspNetCore.Pagination.