Pagination
La pagination fait référence à la récupération des résultats sous forme de pages, plutôt qu’en une seule fois. Cette opération est généralement effectuée pour les jeux de résultats volumineux, où une interface utilisateur s’affiche, et permet à l’utilisateur de naviguer vers la page de résultats suivante ou précédente.
Avertissement
Quelle que soit la méthode de pagination utilisée, assurez-vous toujours que votre commande est entièrement unique. Par exemple, si les résultats sont classés uniquement par date, mais qu’il peut y avoir plusieurs résultats avec la même date, les résultats peuvent être ignorés lors de la pagination, car ils sont triés différemment entre deux requêtes de pagination. Le classement par date et ID (ou toute autre propriété unique ou combinaison de propriétés) rend l’ordre entièrement unique et évite ce problème. Notez que les bases de données relationnelles n’appliquent aucun ordre par défaut, même sur la clé primaire.
Remarque
Azure Cosmos DB dispose de son propre mécanisme de pagination, consultez la page de documentation dédiée.
Pagination de décalage
Une façon courante de mettre en œuvre la pagination dans les bases de données consiste à utiliser les opérateurs LINQ Skip
et Take
(OFFSET
et LIMIT
en SQL). Étant donné une taille de page de 10 résultats, la troisième page peut être extraite avec EF Core comme suit :
var position = 20;
var nextPage = context.Posts
.OrderBy(b => b.PostId)
.Skip(position)
.Take(10)
.ToList();
Malheureusement, bien que cette technique soit très intuitive, elle présente également des lacunes graves :
- La base de données doit toujours traiter les 20 premières entrées, même si elles ne sont pas retournées à l’application ; cela crée une charge de calcul potentiellement significative qui augmente avec le nombre de lignes ignorées.
- Si des mises à jour se produisent simultanément, votre pagination peut finir par ignorer certaines entrées ou les afficher deux fois. Par exemple, si une entrée est supprimée lorsque l’utilisateur passe de la page 2 à 3, l’ensemble de résultats « se déplace vers le haut » et une entrée est ignorée.
Pagination du jeu de clés
L’alternative recommandée à la pagination basée sur le décalage ( parfois appelée pagination de jeu de clés ou pagination basée sur la recherche ) consiste simplement à utiliser une clause WHERE
pour ignorer les lignes, au lieu d’un décalage. Cela signifie que vous devez mémoriser les valeurs pertinentes de la dernière entrée extraite (au lieu de son décalage) et demander les lignes suivantes après cette ligne. Par exemple, en supposant que la dernière entrée de la dernière page que nous avons extraite avait une valeur d’ID de 55, nous allons simplement effectuer les opérations suivantes :
var lastId = 55;
var nextPage = context.Posts
.OrderBy(b => b.PostId)
.Where(b => b.PostId > lastId)
.Take(10)
.ToList();
En supposant qu’un index soit défini sur PostId
, cette requête est très efficace et n’est pas sensible aux modifications simultanées qui se produisent dans des valeurs d’ID inférieures.
La pagination du jeu de clés est appropriée pour les interfaces de pagination où l’utilisateur navigue vers l’avant et vers l’arrière, mais ne prend pas en charge l’accès aléatoire, où l’utilisateur peut accéder à n’importe quelle page spécifique. La pagination d’accès aléatoire nécessite l’utilisation de la pagination de décalage, comme expliqué ci-dessus ; en raison des lacunes de la pagination de décalage, envisagez soigneusement si la pagination d’accès aléatoire est réellement nécessaire pour votre cas d’usage, ou si la navigation de page suivante/précédente est suffisante. Si la pagination d’accès aléatoire est nécessaire, une implémentation robuste peut utiliser la pagination du jeu de clés lors de la navigation vers la page suivante/précédente, et décaler la navigation lors du saut vers une autre page.
Plusieurs clés de pagination
Lorsque vous utilisez la pagination du jeu de clés, il est fréquemment nécessaire de commander plusieurs propriétés. Par exemple, la requête suivante pagine par date et 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();
Cela garantit que la page suivante sélectionne exactement l’endroit où l’autre s’est terminé. À mesure que d’autres clés de classement sont ajoutées, des clauses supplémentaires peuvent être ajoutées.
Remarque
La plupart des bases de données SQL prennent en charge une version plus simple et plus efficace des versions ci-dessus, en utilisant valeurs de ligne: WHERE (Date, Id) > (@lastDate, @lastId)
. EF Core ne prend actuellement pas en charge l’expression dans les requêtes LINQ, il est suivi par #26822.
Index
Comme pour toute autre requête, l’indexation appropriée est essentielle pour de bonnes performances : veillez à disposer d’index en place qui correspondent à votre ordre de pagination. Si vous commandez plusieurs colonnes, un index sur ces plusieurs colonnes peut être défini ; il s’agit d’un index composite.
Pour plus d’informations, consultez la page de documentation sur les index.
Ressources supplémentaires
- Pour en savoir plus sur les lacunes de la pagination basée sur le décalage et sur la pagination du jeu de clés, consultez ce billet.
- Session de standup de la communauté de données .NET où nous abordons la pagination et la démonstration de tous les concepts ci-dessus.
- Présentation approfondie technique comparer la pagination de décalage et d’ensemble de clés. Bien que le contenu traite également de la base de données PostgreSQL, les informations générales sont valides pour d’autres bases de données relationnelles.
- Pour obtenir des extensions au-dessus d’EF Core qui simplifient la pagination des jeux de clés, consultez MR. EntityFrameworkCore.KeysetPagination et MR. AspNetCore.Pagination.