Stronicowanie kolekcji za pomocą LINQ

84

Jak przeglądasz kolekcję w LINQ, biorąc pod uwagę, że masz a startIndexi count?

Nick Berardi
źródło

Odpowiedzi:

43

Kilka miesięcy temu napisałem post na blogu o interfejsach Fluent i LINQ, w których zastosowano metodę rozszerzenia IQueryable<T>i inną klasę, aby zapewnić następujący naturalny sposób podziału na strony kolekcji LINQ.

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

Możesz pobrać kod ze strony MSDN Code Gallery: Pipelines, Filters, Fluent API i LINQ to SQL .

Mike Minutillo
źródło
64

Jest to bardzo proste dzięki metodom Skipi Takerozszerzającym.

var query = from i in ideas
            select i;

var paggedCollection = query.Skip(startIndex).Take(count);
Nick Berardi
źródło
3
Uważam, że można zrobić coś takiego. Może ma odpowiedź, ale może chce zobaczyć, co mogą wymyślić również inni ludzie.
Outlaw Programmer
11
Zostało to pierwotnie opublikowane w pierwszym dniu okresu beta StackOverflow, czyli 66 dla identyfikatora artykułu. Testowałem system dla Jeffa. Poza tym wydawało się, że są to przydatne informacje zamiast zwykłego bzdura testowego, który czasami wychodzi z testów beta.
Nick Berardi
14

Rozwiązałem to trochę inaczej niż to, co mają inni, ponieważ musiałem stworzyć własny paginator, z repeaterem. Więc najpierw utworzyłem zbiór numerów stron dla kolekcji elementów, które mam:

// assumes that the item collection is "myItems"

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

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

Używając tego, mogłem łatwo podzielić kolekcję elementów na zbiór „stron”. Strona w tym przypadku to po prostu zbiór elementów ( IEnumerable<Item>). Oto jak możesz to zrobić używając Skipi Takerazem z wyborem indeksu z pageRangeutworzonego powyżej:

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

Oczywiście musisz traktować każdą stronę jako dodatkową kolekcję, ale np. Jeśli zagnieżdżasz repetytory, jest to w rzeczywistości łatwe w obsłudze.


Jedno-liner TLDR wersja byłoby to:

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

Które można wykorzystać w ten sposób:

for (Enumerable<Item> page : pages) 
{
    // handle page

    for (Item item : page) 
    {
        // handle item in page
    }
}
Spoike
źródło
10

To pytanie jest dość stare, ale chciałem opublikować mój algorytm stronicowania, który pokazuje całą procedurę (w tym interakcję użytkownika).

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);

Jeśli jednak zależy Ci na wydajności, a w kodzie produkcyjnym wszyscy zależy nam na wydajności, nie powinieneś używać stronicowania LINQ, jak pokazano powyżej, ale raczej podstawy IEnumeratordo samodzielnego zaimplementowania stronicowania. W rzeczywistości jest tak prosty, jak algorytm LINQ pokazany powyżej, ale bardziej wydajny:

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);
}

Objaśnienie: Wadą Skip()wielokrotnego używania w „sposób kaskadowy” jest to, że tak naprawdę nie przechowuje „wskaźnika” iteracji, w którym był ostatnio pomijany. - Zamiast tego oryginalna sekwencja będzie poprzedzona wywołaniami pominięcia, co doprowadzi do ciągłego „konsumowania” już „wykorzystanych” stron. - Możesz to udowodnić, tworząc sekwencję ideastak, aby przynosiła efekty uboczne. -> Nawet jeśli pominąłeś 10-20 i 20-30 i chcesz przetworzyć 40+, zobaczysz, że wszystkie efekty uboczne 10-30 są wykonywane ponownie, zanim zaczniesz iterować 40+. Wariant korzystający IEnumerablebezpośrednio z interfejsu zapamięta pozycję końca ostatniej logicznej strony, więc nie jest potrzebne żadne wyraźne przeskakiwanie, a efekty uboczne nie będą się powtarzać.

Nico
źródło