Paginación de una colección con LINQ

84

¿Cómo recorre una colección en LINQ dado que tiene una startIndexy una count?

Nick Berardi
fuente

Respuestas:

43

Hace unos meses escribí una publicación de blog sobre Fluent Interfaces y LINQ que usaba un método de extensión IQueryable<T>y otra clase para proporcionar la siguiente forma natural de paginar una colección LINQ.

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

Puede obtener el código de la página de la galería de códigos de MSDN: Pipelines, Filters, Fluent API y LINQ to SQL .

Mike Minutillo
fuente
64

Es muy simple con los métodos de extensión Skipy Take.

var query = from i in ideas
            select i;

var paggedCollection = query.Skip(startIndex).Take(count);
Nick Berardi
fuente
3
Creo que está bien hacer algo como esto. Puede que tenga una respuesta, pero tal vez quiera ver qué se le ocurre a otras personas.
Programador fuera de la ley
11
Esto se publicó originalmente durante el primer día del período beta de StackOverflow, por lo tanto, el 66 para el ID del artículo. Estaba probando el sistema, por Jeff. Además, parecía información útil en lugar de la basura de prueba habitual que a veces surge de las pruebas beta.
Nick Berardi
14

Resolví esto de manera un poco diferente a lo que tienen los demás, ya que tuve que hacer mi propio paginador, con un repetidor. Entonces, primero hice una colección de números de página para la colección de elementos que tengo:

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

Con esto, podría dividir fácilmente la colección de elementos en una colección de "páginas". En este caso, una página es solo una colección de elementos ( IEnumerable<Item>). Así es como puede hacerlo usando Skipy Takejunto con la selección del índice del pageRangecreado anteriormente:

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

Por supuesto, debe manejar cada página como una colección adicional pero, por ejemplo, si está anidando repetidores, esto es realmente fácil de manejar.


La versión TLDR de una sola línea sería la siguiente:

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

Que se puede utilizar como este:

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

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

Esta pregunta es algo antigua, pero quería publicar mi algoritmo de paginación que muestra todo el procedimiento (incluida la interacción del usuario).

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

Sin embargo, si busca rendimiento, y en el código de producción, todos buscamos rendimiento, no debe usar la paginación de LINQ como se muestra arriba, sino el subyacente IEnumeratorpara implementar la paginación usted mismo. De hecho, es tan simple como el algoritmo LINQ que se muestra arriba, pero más eficaz:

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

Explicación: La desventaja de usar Skip()varias veces en "forma en cascada" es que realmente no almacenará el "puntero" de la iteración, donde se omitió por última vez. - En su lugar, la secuencia original se cargará al principio con llamadas saltadas, lo que conducirá a "consumir" las páginas ya "consumidas" una y otra vez. - Puedes probarlo tú mismo, cuando creas la secuencia ideaspara que produzca efectos secundarios. -> Incluso si ha omitido 10-20 y 20-30 y desea procesar 40+, verá que todos los efectos secundarios de 10-30 se ejecutan nuevamente, antes de comenzar a iterar 40+. La variante que usa IEnumerablela interfaz directamente recordará en cambio la posición del final de la última página lógica, por lo que no es necesario omitirlos explícitamente y los efectos secundarios no se repetirán.

Nico
fuente