Paginación de una colección con LINQ


¿Cómo hojear una colección en LINQ dado que tiene un startIndex y un count?

Author: mattytommo, 2008-08-01

4 answers

Hace unos meses escribí una entrada de blog sobre Fluent Interfaces y LINQ que utiliza un Método de extensión en 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 desde la página de Galería de código de MSDN: Canalizaciones, Filtros, Fluent API y LINQ a SQL.

 38
Author: Mike Minutillo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-09 14:59:49

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

var query = from i in ideas
            select i;

var paggedCollection = query.Skip(startIndex).Take(count);
 62
Author: Nick Berardi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-04 09:04:26

Resolví esto un poco diferente de lo que los otros tienen, ya que tuve que hacer mi propio paginador, con un repetidor. Así que primero hice una colección de números de página para la colección de artículos 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]

Usando esto podría particionar fácilmente la colección de elementos en una colección de "páginas". Una página en este caso es solo una colección de elementos (IEnumerable<Item>). Así es como puedes hacerlo usando Skip y Take junto con seleccionar el índice de la pageRange creada anteriormente:

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

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


La versión de TLDR one-liner sería la siguiente:

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

Que se puede usar como esto:

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

    for (Item item : page) 
    {
        // handle item in page
    }
}
 12
Author: Spoike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-20 13:55:22

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 buscas rendimiento, y en código de producción, todos buscamos rendimiento, no deberías usar la paginación de LINQ como se muestra arriba, sino la IEnumerator subyacente para implementar la paginación tú mismo. De hecho, es tan simple como el algoritmo LINQ mostrado anteriormente, pero más eficiente:

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 de una "manera 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á con llamadas de salto, lo que llevará a "consumir" las páginas ya "consumidas" una y otra vez. - Puedes demostrarlo tú mismo, cuando creas la secuencia ideas para que produzca efectos secundarios. - > Incluso si se ha saltado 10-20 y 20-30 y desea procesar más de 40, verá todos los efectos secundarios de 10-30 se ejecuta de nuevo, antes de empezar a iterar 40+. La variante que usa la interfaz de IEnumerable directamente, en su lugar recordará la posición del final de la última página lógica, por lo que no se necesita omitir explícitamente y los efectos secundarios no se repetirán.

 9
Author: Nico,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-03 18:41:55