linq - paginar - paginador c#
Paginación de una colección con LINQ (4)
¿Cómo navega por una colección en LINQ dado que tiene un
startIndex
y un
count
?
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);
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 estamos después del rendimiento, no debe usar la paginación de LINQ como se muestra arriba, sino el
IEnumerator
subyacente para 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 de forma "en cascada" es que realmente no almacenará el "puntero" de la iteración, donde se omitió por última vez.
- En cambio, la secuencia original se cargará por adelantado con llamadas salteadas, lo que conducirá a "consumir" las páginas ya "consumidas" una y otra vez.
- Puede probarlo usted mismo cuando crea las
ideas
secuencia para que produzca efectos secundarios.
-> Incluso si ha omitido 10-20 y 20-30 y desea procesar más de 40, verá que todos los efectos secundarios de 10-30 se ejecutan nuevamente, antes de comenzar a repetir 40+.
La variante que usa la interfaz de
IEnumerable
directamente, en cambio, 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.
Hace unos meses escribí una publicación de blog sobre Fluent Interfaces y LINQ que usaba 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 de la página de la galería de códigos de MSDN: tuberías, filtros, API fluida y LINQ to SQL .
Resolví esto un poco diferente de lo que tienen los demás, 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]
Utilizando esto, podría fácilmente dividir 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 puede hacerlo usando
Skip
y
Take
junto con la selección del índice de la página
pageRange
creado 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 línea sería esta:
var pages = Enumerable
.Range(0, pageCount)
.Select((index) => myItems.Skip(index*PageSize).Take(PageSize));
Que se puede usar así:
for (Enumerable<Item> page : pages)
{
// handle page
for (Item item : page)
{
// handle item in page
}
}