¿Cómo recorre una colección en LINQ dado que tiene una startIndex
y una count
?
84
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 .
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);
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 usandoSkip
yTake
junto con la selección del índice delpageRange
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 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 } }
fuente
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
IEnumerator
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 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 secuenciaideas
para 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 usaIEnumerable
la 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.fuente