Paging une collection avec LINQ

Comment parcourir une collection dans LINQ étant donné que vous avez un startIndex et un count ?

0
ajouté édité
Vues: 17

4 Réponses

C'est très simple avec les méthodes d'extension Skip et Take .

var query = from i in ideas
            select i;

var paggedCollection = query.Skip(startIndex).Take(count);
0
ajouté
Cela a été publié à l'origine pendant le premier jour de la période bêta de StackOverflow, donc le 66 pour l'article ID. Je testais le système, pour Jeff. De plus, cela semblait être une information utile à la place de la merde de test habituelle qui sort parfois des tests bêta.
ajouté l'auteur Nick Berardi, source
Je crois que c'est OK de faire quelque chose comme ça. Il pourrait avoir une réponse, mais peut-être veut-il voir ce que les autres peuvent proposer.
ajouté l'auteur Outlaw Programmer, source

A few months back I wrote a blog post about Fluent Interfaces and LINQ which used an Extension Method on IQueryable and another class to provide the following natural way of paginating a LINQ collection.

var query = from i in ideas
            select i;
var pagedCollection = query.InPagesOf(10);
var pageOfIdeas = pagedCollection.Page(2);

Vous pouvez obtenir le code à partir de la page MSDN Code Gallery: Pipelines, filtres, API Fluent et LINQ to SQL .

0
ajouté

Cette question est un peu ancienne, mais je voulais poster mon algorithme de pagination qui montre toute la procédure (y compris l'interaction de l'utilisateur).

const int pageSize = 10;
const int count = 100;
const int startIndex = 20;

int took = 0;
bool getNextPage;
var page = ideas.Skip(startIndex);

do
{
    Console.WriteLine("Page {0}:", (took / pageSize) + 1);
    foreach (var idea in page.Take(pageSize))
    {
        Console.WriteLine(idea);
    }

    took += pageSize;
    if (took < count)
    {
        Console.WriteLine("Next page (y/n)?");
        char answer = Console.ReadLine().FirstOrDefault();
        getNextPage = default(char) != answer && 'y' == char.ToLowerInvariant(answer);

        if (getNextPage)
        {
            page = page.Skip(pageSize);
        }
    }
}
while (getNextPage && took < count);

Cependant, si vous recherchez des performances et du code de production, nous ne voulons pas utiliser la pagination de LINQ comme indiqué ci-dessus, mais plutôt le IEnumerator sous-jacent pour implémenter la pagination. En fait, il est aussi simple que l'algorithme LINQ montré ci-dessus, mais plus performant:

const int pageSize = 10;
const int count = 100;
const int startIndex = 20;

int took = 0;
bool getNextPage = true;
using (var page = ideas.Skip(startIndex).GetEnumerator())
{
    do 
    {
        Console.WriteLine("Page {0}:", (took / pageSize) + 1);

        int currentPageItemNo = 0;
        while (currentPageItemNo++ < pageSize && page.MoveNext())
        {
            var idea = page.Current;
            Console.WriteLine(idea);
        }

        took += pageSize;
        if (took < count)
        {
            Console.WriteLine("Next page (y/n)?");
            char answer = Console.ReadLine().FirstOrDefault();
            getNextPage = default(char) != answer && 'y' == char.ToLowerInvariant(answer);
        }
    }
    while (getNextPage && took < count);
}

Explanation: The downside of using Skip() for multiple times in a "cascading manner" is, that it will not really store the "pointer" of the iteration, where it was last skipped. - Instead the original sequence will be front-loaded with skip calls, which will lead to "consuming" the already "consumed" pages over and over again. - You can prove that yourself, when you create the sequence ideas so that it yields side effects. -> Even if you have skipped 10-20 and 20-30 and want to process 40+, you'll see all side effects of 10-30 being executed again, before you start iterating 40+. The variant using IEnumerable's interface directly, will instead remember the position of the end of the last logical page, so no explicit skipping is needed and side effects won't be repeated.

0
ajouté

J'ai résolu ce problème un peu différemment de ce que les autres ont eu car je devais faire mon propre paginateur, avec un répéteur. J'ai donc d'abord fait une collection de numéros de pages pour la collection d'objets que j'ai:

// assumes that the item collection is "myItems"

int pageCount = (myItems.Count + PageSize - 1) / PageSize;

IEnumerable pageRange = Enumerable.Range(1, pageCount);
   // pageRange contains [1, 2, ... , pageCount]

Using this I could easily partition the item collection into a collection of "pages". A page in this case is just a collection of items (IEnumerable). This is how you can do it using Skip and Take together with selecting the index from the pageRange created above:

IEnumerable> pageRange
    .Select((page, index) => 
        myItems
            .Skip(index*PageSize)
            .Take(PageSize));

Bien sûr, vous devez gérer chaque page comme une collection supplémentaire, mais par ex. Si vous imbriquez des répéteurs, alors c'est vraiment facile à gérer.


La version one-liner TLDR serait la suivante:

var pages = Enumerable
    .Range(0, pageCount)
    .Select((index) => myItems.Skip(index*PageSize).Take(PageSize));

Ce qui peut être utilisé comme ceci:

for (Enumerable page : pages) 
{
    // handle page

    for (Item item : page) 
    {
        // handle item in page
    }
}
0
ajouté