Paginar uma coleção com LINQ

84

Como você folheia uma coleção no LINQ, considerando que você tem um startIndexe um count?

Nick Berardi
fonte

Respostas:

43

Alguns meses atrás, escrevi uma postagem no blog sobre interfaces fluentes e LINQ que usava um método de extensão em IQueryable<T>e outra classe para fornecer a seguinte maneira natural de paginar uma coleção LINQ.

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

Você pode obter o código na página MSDN Code Gallery: Pipelines, Filters, Fluent API e LINQ to SQL .

Mike Minutillo
fonte
64

É muito simples com os métodos de extensão Skipe Take.

var query = from i in ideas
            select i;

var paggedCollection = query.Skip(startIndex).Take(count);
Nick Berardi
fonte
3
Eu acredito que está tudo bem fazer algo assim. Ele pode ter uma resposta, mas talvez também queira ver o que outras pessoas podem descobrir.
Outlaw Programmer
11
Isso foi postado originalmente durante o primeiro dia do período beta do StackOverflow, portanto, o 66 para o ID do artigo. Eu estava testando o sistema para o Jeff. Além disso, parecia uma informação útil em vez da porcaria de teste usual que às vezes sai do teste beta.
Nick Berardi
14

Resolvi isso de forma um pouco diferente do que os outros, pois tive que fazer meu próprio paginador, com repetidor. Então, primeiro fiz uma coleção de números de página para a coleção de itens que tenho:

// 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 isso, eu poderia facilmente particionar a coleção de itens em uma coleção de "páginas". Uma página, neste caso, é apenas uma coleção de itens ( IEnumerable<Item>). É assim que você pode fazer usando Skipe Takejunto com a seleção do índice pageRangecriado acima:

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

Claro que você tem que lidar com cada página como uma coleção adicional, mas, por exemplo, se você estiver aninhando repetidores, isso é realmente fácil de lidar.


A versão TLDR de uma linha seria esta:

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

Que pode ser usado assim:

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

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

Esta pergunta é um pouco antiga, mas eu queria postar meu algoritmo de paginação que mostra todo o procedimento (incluindo a interação do usuário).

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

No entanto, se você está atrás de desempenho, e no código de produção, estamos todos atrás de desempenho, você não deve usar a paginação do LINQ como mostrado acima, mas sim a base IEnumeratorpara implementar você mesmo a paginação. Na verdade, é tão simples quanto o algoritmo LINQ mostrado acima, mas tem mais desempenho:

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

Explicação: A desvantagem de usar Skip()várias vezes em uma "maneira em cascata" é que ele não armazenará realmente o "ponteiro" da iteração onde foi ignorado pela última vez. - Em vez disso, a sequência original será carregada antecipadamente com chamadas ignoradas, o que levará ao "consumo" das páginas já "consumidas" continuamente. - Você mesmo pode provar isso, ao criar a sequência ideaspara que ela produza efeitos colaterais. -> Mesmo se você tiver pulado 10-20 e 20-30 e quiser processar 40+, verá todos os efeitos colaterais de 10-30 sendo executados novamente, antes de começar a iterar 40+. A variante usando IEnumerablea interface de diretamente, em vez disso, lembrará a posição do final da última página lógica, de modo que nenhum salto explícito é necessário e os efeitos colaterais não se repetirão.

Nico
fonte