2010-11-02 3 views
3

Je travaille sur l'ajout d'un HtmlHelper pour la pagination, mais je ne suis pas sûr de l'endroit approprié et/ou le plus bénéfique pour mettre certaines parties du code de pagination du point de vue de la performance et de la maintenabilité. Je ne suis pas certain que les parties Skip(), Take() et Count() de la manipulation de données Linq to SQL doivent rester dans le référentiel ou dans le contrôleur.ASP.NET MVC2 LINQ - Modèle de référentiel, où doit aller le code de pagination?

Je ne sais pas non plus si leur commande et l'endroit où ils sont utilisés affectent les performances de quelque façon que ce soit.

S'ils vivent dans le dépôt de ma compréhension Voici comment cela fonctionnerait:
1. je passerais le pageIndex et pageSize comme arguments à la méthode du dépôt qui saisit les données de la base de données.
2. Ensuite, récupérez l'ensemble de données complet de la base de données.
3. Ensuite, stockez le nombre de TotalItems de cet ensemble de données complet dans une variable.
4. Ensuite, appliquez Skip() et Take() pour que l'ensemble de données ne conserve que la page dont j'ai besoin.
5. Afficher l'ensemble de données partiel sous la forme d'une seule page dans la vue. Si elles vivent dans le contrôleur à ma compréhension, voici comment cela fonctionnerait: 1. Je voudrais récupérer l'ensemble complet du dépôt et le stocker dans une variable à l'intérieur du contrôleur. 2. Ensuite, obtenez le nombre de TotalItems pour l'ensemble de données complet. 3. Ensuite, appliquez Skip() et Take() pour que l'ensemble de données ne conserve que la page dont j'ai besoin. 4. Affichez l'ensemble de données partiel sous la forme d'une seule page dans la vue.

A l'intérieur du contrôleur (je me rends compte que je vais mal obtenir la page compte ici et non TotalItems):


Character[] charactersToShow = charactersRepository.GetCharactersByRank(this.PageIndex, this.PageSize); 
RankViewModel viewModel = new RankViewModel 
{ 
    Characters = charactersToShow, 
    PaginationInfo = new PaginationInfo 
    { 
     CurrentPage = this.PageIndex, 
     ItemsPerPage = this.PageSize, 
     TotalItems = charactersToShow.Count() 
    } 
}; 

A l'intérieur du dépôt:


public Character[] GetCharactersByRank(int PageIndex, int PageSize) 
{ 
    IQueryable characters = (from c in db.Characters 
     orderby c.Kill descending 
     select new Character { 
      CharID = c.CharID, 
      CharName = c.CharName, 
      Level = c.Level 
     }); 
    characters = PageIndex > 1 ? characters.Skip((PageIndex - 1) * PageSize).Take(PageSize) : characters.Take(PageSize); 
    return characters.ToArray(); 
} 

Ce code est un exemple partiel de la façon dont je implémentait le code Skip(), Take() et Count() vivant dans le référentiel. Je n'ai pas réellement implémenté l'obtention et le renvoi des TotalItems parce que je me suis rendu compte que je ne connaissais pas l'endroit approprié pour mettre cela.

Une partie de la raison pour laquelle je ne sais pas où les mettre est que je ne sais pas comment Linq to SQL fonctionne sous le capot, et donc je ne sais pas comment optimiser les performances. Je ne sais pas si c'est même un problème dans cette affaire.

Est-ce qu'il doit saisir tous les enregistrements de la base de données lorsque vous faites un .Count() sur Linq to SQL? Est-ce qu'il doit faire des requêtes séparées si je fais un .Count(), puis plus tard faire un .Skip() et .Take()? Y at-il des problèmes de performances possibles avec l'utilisation de .Count() avant un .Skip() et .Take()?

C'est la première fois que j'utilise un ORM, donc je ne sais pas à quoi m'attendre. Je sais que je peux voir les requêtes Linq à SQL est en cours d'exécution, mais je pense qu'écouter quelqu'un avec de l'expérience dans ce cas serait une meilleure utilisation de mon temps.

Je voudrais comprendre cela plus en profondeur, toute idée serait appréciée.

+0

pour répondre * une * de vos questions, oui - Il faut faire 2 appels DB. Le seul moyen d'éviter cela est de faire le paging 100% dans la base de données elle-même. – RPM1984

Répondre

2

J'ai trouvé sur le NerdDinner site Marko mentionné ci-dessus et il a répondu à beaucoup de mes questions.

De NerdDinner en bas de page 8:

IQueryable est une fonctionnalité très puissante qui permet une variété de scénarios d'exécution différée intéressants (comme les requêtes basées sur la pagination et la composition). Comme avec toutes les fonctionnalités puissantes, vous devez être prudent avec la façon dont vous l'utilisez et assurez-vous qu'il n'est pas abusé. Il est important de reconnaître que le renvoi d'un résultat IQueryable à partir de votre référentiel permet au code appelant de l'ajouter sur des méthodes d'opérateur chaîné et de participer ainsi à l'exécution finale de la requête. Si vous ne voulez pas donner cette capacité au code appelant, vous devez retourner les résultats IList ou IEnumerable - qui contiennent les résultats d'une requête déjà exécutée. Pour les scénarios de pagination, vous devez pousser la logique de pagination des données dans la méthode du référentiel appelée. Dans ce scénario, nous pourrions mettre à jour nos FindUpcomingDinners() méthode de recherche d'avoir une signature qui soit retourné un PaginatedList:
PaginatedList < Dîner> FindUpcomingDinners (int pageIndex, int pageSize) {}
Ou retourner un IList, et d'utiliser un "totalCount" out param pour retourner le nombre total de dîners:
IList FindUpcomingDinners (int pageIndex, int pageSize, out int totalCount) {}

2

Je garde une classe générique PaginatedList dans mon dossier Helpers où je mets également d'autres classes Helper.

La PaginatedList est directement sortie de NerdDinner, et elle ressemble à ceci.

public class PaginatedList<T>: List<T> 
{ 
    public int PageIndex { get; private set; } 
    public int PageSize { get; private set; } 
    public int TotalCount { get; private set; } 
    public int TotalPages { get; private set; } 

    public PaginatedList(IQueryable<T> source, int pageIndex, int pageSize) 
    { 
     PageIndex = pageIndex; 
     PageSize = pageSize; 
     TotalCount = source.Count(); 
     TotalPages = (int) Math.Ceiling(TotalCount/(double)PageSize); 

     this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize)); 
    } 

    public bool HasPreviousPage 
    { 
     get 
     { 
      return (PageIndex > 0); 
     } 
    } 

    public bool HasNextPage 
    { 
     get 
     { 
      return (PageIndex + 1 < TotalPages); 
     } 
    } 
} 
+0

Le problème avec ceci est que 2 requêtes sont lancées à chaque fois - une pour le '.Count()', une autre pour le combo 'Skip/Take'. Je dois encore trouver un moyen d'éviter cela cependant. Seule une autre option consiste à faire le pb db-side. – RPM1984

+0

J'ai trouvé ce que je cherchais à lire sur le site NerdDinner, merci pour la référence :) – Sgraffite

Questions connexes