web-dev-qa-db-fra.com

La pagination avec PagedList est-elle efficace?

Je cherche depuis longtemps à implémenter la pagination et j’ai trouvé ce tutoriel pour la pagination avec MVC: ASP.NET MVC Paging Done Perfectly

Maintenant, dans cette solution, j'interroge la base de données pour le ensemble complet de clients, puis je renvoie une liste paginée de clients au lieu d'une liste normale.

Je trouve cela inquiétant, car je n’ai l’intention d’afficher que 10 ou 20 entrées par page, et ma base de données en contiendra facilement plus d’un million. Ainsi, interroger toute la base de données chaque fois que je veux afficher la page Index semble être au mieux une solution médiocre.

Si je comprends quelque chose qui ne va pas, n'hésitez pas à me couper maintenant, mais pour moi, cette solution est tout sauf parfaite.

Ai-je mal compris quelque chose? Existe-t-il une solution ou une bibliothèque plus efficace pour la pagination avec MVC?

13
Flame_Phoenix

Naturellement, la pagination nécessitera la connaissance du nombre total de résultats pour que la logique détermine le nombre de pages, etc. Cependant, au lieu de réduire tous les résultats, générez simplement votre requête à la base de données pour renvoyer le montant paginé (par exemple 30). ainsi que le compte de tous les résultats.

Par exemple, si vous utilisiez Entity Framework ou LINQ2SQL, vous pourriez faire quelque chose comme ceci.

IQueryable<Result> allResults = MyRepository.RetrieveAll();

var resultGroup = allResults.OrderByDescending(r => r.DatePosted)
                                               .Skip(60)
                                               .Take(30)
                                               .GroupBy(p => new {Total = allResults.Count()})
                                               .First();

var results = new ResultObject
{
    ResultCount = resultGroup.Key.Total,
    Results = resultGrouping.Select(r => r)
};

Parce que nous n'avons pas fait de .ToList () sur notre jeu de résultats jusqu'à ce que nous ayons finalisé ce que nous voulons, nous n'avons pas mis les résultats en mémoire. Ceci est fait lorsque nous appelons le .First () sur notre jeu de résultats.

Enfin, notre objet avec lequel nous nous retrouvons (ResultObject) peut être utilisé pour faire la pagination plus tard. Comme nous avons le décompte, nous savons déjà sur quelle page nous nous trouvons (3, nous en avons sauté 60, avec 30 par page) et nous avons les résultats à afficher.

Lectures complémentaires et informations

Comment: parcourir les résultats d'une requête

Pagination côté serveur avec cadre d'entité

7
JonE

L'exemple sur github montre qu'il utilise un IQueryable qui est ensuite utilisé par ToPagedList (), ce qui implique que le code est assez optimisé et ne renverra pas en soi tous les enregistrements ...

En regardant le code de la classe PagedList

// superset is the IQueryable.
TotalItemCount = superset == null ? 0 : superset.Count();

// add items to internal list
if (superset != null && TotalItemCount > 0)
    Subset.AddRange(pageNumber == 1
    ? superset.Skip(0).Take(pageSize).ToList()
    : superset.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToList()

Comme vous pouvez le constater, il utilise déjà les méthodes de pagination recommandées côté serveur, telles que sauter, prendre et ensuite effectuer une liste ToList ().

Toutefois, si vous ne travailliez pas avec un IQueryable, c’est-à-dire un IEnumerable, le code suivant est utilisé:

/// <summary>
/// Initializes a new instance of the <see cref="PagedList{T}"/> class that divides the supplied superset into subsets the size of the supplied pageSize. The instance then only containes the objects contained in the subset specified by index.
/// </summary>
/// <param name="superset">The collection of objects to be divided into subsets. If the collection implements <see cref="IQueryable{T}"/>, it will be treated as such.</param>
/// <param name="pageNumber">The one-based index of the subset of objects to be contained by this instance.</param>
/// <param name="pageSize">The maximum size of any individual subset.</param>
/// <exception cref="ArgumentOutOfRangeException">The specified index cannot be less than zero.</exception>
/// <exception cref="ArgumentOutOfRangeException">The specified page size cannot be less than one.</exception>
public PagedList(IEnumerable<T> superset, int pageNumber, int pageSize)
        : this(superset.AsQueryable<T>(), pageNumber, pageSize)
    {
    }

Le problème est que, selon le filtrage utilisé pour obtenir le IEnumerable en premier lieu, il peut contenir tous les enregistrements, utilisez donc un IQueryable si possible pour optimiser les performances de PagedList.

4
Paul Zahra

Le tutoriel lié semble étrange, car il utilise un List<Client>. Cela ramènera effectivement tous les clients en mémoire, puis page par page. Au lieu de cela, vous devriez rechercher des méthodes qui utilisent IQueryable<T>, en particulier Skip et Take, de sorte que la pagination devrait ressembler à

IQueryable<Client> clients = repo.GetClients();          // lazy load - does nothing
List<Client> paged = clients.Skip(20).Take(10).ToList(); // execute final SQL

En fonction des mappeurs que vous utilisez, vous trouverez des méthodes similaires dans EF, NHibernate, Linq-to-SQL, etc.

2
oleksii

Vous pouvez implémenter la pagination dans votre application de trois manières:

  • Forcez votre Repository à renvoyer les DTO avec un minimum de données au client et utilisez certains des plug-ins jquery fournissant eux-mêmes la pagination. C'est un moyen simple, mais parfois (comme dans votre cas), ce n'est pas une option. Donc, vous devez implémenter la pagination côté serveur
  • Mettre en cache toute la collection et renvoyer la page nécessaire avec l'extension LINQ. Je pense que beaucoup de référentiels et d'ORM le font en interne (cela n'a pas fonctionné avec Entity Framework, je ne peux pas le dire avec certitude). Le problème avec cette solution est que vous devez synchroniser le cache et la base de données, et que vous devez disposer d’un serveur disposant de suffisamment de mémoire pour stocker toutes vos données (ou accéder au cloud, ou quelque chose du genre). Comme dans d'autres réponses, vous pouvez ignorer les données inutiles avec le travail paresseux IEnumerable, de sorte que vous n'avez pas besoin de mettre en cache la collection.
  • Implémenter la pagination côté DB. Si vous utilisez SQL, vous pouvez utiliser la construction ROW_NUMBER, elle fonctionne soit dans MS SQL ou dans Oracle ou dans MySQL (pas ROW_NUMBER lui-même, uniquement analogique). Si vous avez la solution NoSQL, vous devez alors vérifier la documentation.
1
VMAtm

Si vous allez à la page github de l’addon PagedList vous pouvez voir que si vous avez une méthode renvoyant un IQueryable<T>, la magie PagedList peut fonctionner sans que tous les éléments de la base de données soient renvoyés. Si vous ne pouvez pas contrôler ce que la requête de la base de données vous renvoie, vous devez vous fier à d'autres méthodes.

L'exemple de cette page est ceci

public class ProductController : Controller
{
    public object Index(int? page)
    {
        var products = MyProductDataSource.FindAllProducts(); //returns IQueryable<Product> representing an unknown number of products. a thousand maybe?

        var pageNumber = page ?? 1; // if no page was specified in the querystring, default to the first page (1)
        var onePageOfProducts = products.ToPagedList(pageNumber, 25); // will only contain 25 products max because of the pageSize

        ViewBag.OnePageOfProducts = onePageOfProducts;
        return View();
    }
}
1
Loofer

Ce composant (PagedList) fonctionne parfaitement pour un grand nombre d'enregistrements, et chaque fois que vous sélectionnez une page, il appelle 2 fois la base de données. L'un renverra le nombre d'enregistrements et l'autre renverra uniquement les enregistrements de la page sélectionnée. Assurez-vous simplement que vous n'appelez pas la méthode ToList () 

0
foluis