Paging einer Sammlung mit LINQ

84

Wie blättern Sie in einer Sammlung in LINQ, wenn Sie ein startIndexund ein haben count?

Nick Berardi
quelle

Antworten:

43

Vor einigen Monaten schrieb ich einen Blog-Beitrag über Fluent Interfaces und LINQ, in dem eine Erweiterungsmethode für IQueryable<T>und eine andere Klasse verwendet wurden, um die folgende natürliche Methode zur Paginierung einer LINQ-Sammlung bereitzustellen.

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

Sie können den Code von der MSDN-Codegalerieseite abrufen: Pipelines, Filter, Fluent API und LINQ to SQL .

Mike Minutillo
quelle
64

Es ist sehr einfach mit den Skipund TakeErweiterungsmethoden.

var query = from i in ideas
            select i;

var paggedCollection = query.Skip(startIndex).Take(count);
Nick Berardi
quelle
3
Ich glaube, dass es in Ordnung ist, so etwas zu tun. Er könnte eine Antwort haben, aber vielleicht möchte er sehen, was sich andere Leute auch einfallen lassen können.
Outlaw Programmer
11
Dies wurde ursprünglich am ersten Tag der Beta-Phase von StackOverflow veröffentlicht, also die 66 für die Artikel-ID. Ich habe das System für Jeff getestet. Außerdem schien es eine nützliche Information zu sein, anstatt des üblichen Testmistes, der manchmal aus Betatests resultiert.
Nick Berardi
14

Ich habe das etwas anders gelöst als die anderen, da ich meinen eigenen Paginator mit einem Repeater herstellen musste. Also habe ich zuerst eine Sammlung von Seitenzahlen für die Sammlung von Gegenständen erstellt, die ich habe:

// 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]

Auf diese Weise könnte ich die Objektsammlung leicht in eine Sammlung von "Seiten" unterteilen. Eine Seite ist in diesem Fall nur eine Sammlung von Elementen ( IEnumerable<Item>). So können Sie es mit Skipund Takezusammen mit der Auswahl des Index aus dem pageRangeoben erstellten tun :

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

Natürlich müssen Sie jede Seite als zusätzliche Sammlung behandeln, aber wenn Sie beispielsweise Repeater verschachteln, ist dies tatsächlich einfach zu handhaben.


Die einzeilige TLDR- Version wäre folgende:

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

Welches kann wie folgt verwendet werden:

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

    for (Item item : page) 
    {
        // handle item in page
    }
}
Spoike
quelle
10

Diese Frage ist etwas alt, aber ich wollte meinen Paging-Algorithmus veröffentlichen, der die gesamte Prozedur (einschließlich Benutzerinteraktion) zeigt.

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

Wenn Sie jedoch nach Leistung streben und im Produktionscode alle nach Leistung streben, sollten Sie das Laging von LINQ nicht wie oben gezeigt verwenden, sondern den zugrunde liegenden IEnumeratorWert, um das Paging selbst zu implementieren. Tatsächlich ist es so einfach wie der oben gezeigte LINQ-Algorithmus, aber leistungsfähiger:

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

Erläuterung: Der Nachteil einer Skip()mehrfachen "kaskadierenden Verwendung" besteht darin, dass der "Zeiger" der Iteration, in der er zuletzt übersprungen wurde, nicht wirklich gespeichert wird. - Stattdessen wird die ursprüngliche Sequenz mit Sprungaufrufen von vorne geladen, was dazu führt, dass die bereits "verbrauchten" Seiten immer wieder "verbraucht" werden. - Sie können dies selbst beweisen, wenn Sie die Sequenz ideasso erstellen , dass sie Nebenwirkungen hervorruft. -> Selbst wenn Sie 10-20 und 20-30 übersprungen haben und 40+ verarbeiten möchten, werden alle Nebenwirkungen von 10-30 erneut ausgeführt, bevor Sie mit der Iteration von 40+ beginnen. Die Variante, die IEnumerabledie Benutzeroberfläche direkt verwendet, merkt sich stattdessen die Position am Ende der letzten logischen Seite, sodass kein explizites Überspringen erforderlich ist und Nebenwirkungen nicht wiederholt werden.

Nico
quelle