Paginer les données
Introduction
Lorsqu’une requête retourne un grand nombre d’enregistrements, charger toutes les données peut dégrader les performances de l’application. Le serveur de base de données doit lire et retourner un volume important de données, celles-ci doivent ensuite être transférées sur le réseau, puis matérialisées sous la forme d’objets par Entity Framework Core. Ces opérations augmentent le temps d’exécution de la requête et la quantité de mémoire utilisée par l’application.
Dans la plupart des cas, l’utilisateur ne consulte pourtant qu’une partie de ces données. La pagination permet de diviser les résultats en plusieurs pages et de ne récupérer que les enregistrements nécessaires aux traitements ou à l’affichage. Elle réduit ainsi le volume de données traité et transféré à chaque requête, ce qui peut améliorer de manière significative les performances.
Avec Entity Framework Core, la pagination peut être mise en œuvre via les méthodes LINQ Skip et Take. Pour illustrer l'utilisation de ces méthodes, prenons l’exemple d’une application affichant une liste de produits :
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public DateTime CreatedAt { get; set; }
}
L'importance de filtrer et trier les données
Pour obtenir des résultats cohérents, les opérations réalisées dans la requête Linq To Entities doivent être appliquées dans l’ordre suivant :
context.Products
.Where(/* filtres */)
.OrderBy(/* critère de tri */)
.ThenBy(/* critère garantissant un ordre unique */)
.Skip(/* nombre d'enregistrements à ignorer */)
.Take(/* taille de la page */);
Les données sont d’abord filtrées et triées, puis Entity Framework Core ne récupère que les enregistrements appartenant à la page demandée.
Principe de la pagination
La technique de pagination la plus courante repose sur deux méthodes LINQ :
-
Skip : ignore un nombre d’enregistrements.
-
Take : limite le nombre d’enregistrements retournés.
Pour une page de 20 enregistrements, les valeurs obtenues sont les suivantes :
| Page demandée | Enregistrements à ignorer | Enregistrements à obtenir |
|---|---|---|
| 1 | 0 | 20 |
| 2 | 20 | 20 |
| 3 | 40 | 20 |
| 10 | 180 | 20 |
La 3° page des enregistrements peut donc être obtenue de la manière suivante :
List<Product> products = await context.Products
.OrderBy(product => product.Id)
.Skip(40)
.Take(20)
.ToListAsync();
Les méthodes Skip et Take sont traduites dans la requête SQL exécutée par le serveur de base de données. Seuls les enregistrements de la page demandée sont obtenus dans la mémoire de l'application.
De manière plus dynamique, nous pouvons écrire :
int pageNumber = 3;
int pageSize = 20;
decimal maximumPrice = 500;
List<Product> products = await context.Products
.Where(product => product.Price <= maximumPrice)
.OrderBy(product => product.Name)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
L’instruction (pageNumber - 1) * pageSize permet de calculer le nombre d’enregistrements à ignorer.
Amélioration des performances
La pagination permet d'améliorer les performances à plusieurs niveaux :
Serveur de base de données
Le nombre d’enregistrements retournés est limité.
Réseau
Le volume de données à transférer vers l’application diminue.
Entity Framework
Moins d’entités sont matérialisées et éventuellement suivies.
Mémoire
l’application gère moins d’objets, limitant l'encombrement mémoire.
Temps de réponse
L’interface reçoit plus rapidement les données nécessaires et affiche ainsi moins de données.