Пейджинг коллекции с LINQ

Как вы просматриваете коллекцию в LINQ, если у вас есть startIndex и count?

Ответ 1

Несколько месяцев назад я написал сообщение в блоге о Fluent Interfaces и LINQ, в котором использовался метод расширения для IQueryable<T> и другого класса, чтобы обеспечить следующий естественный способ разбиения на страницы коллекции LINQ.

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

Код страницы кода MSDN можно получить: Pipelines, Filters, Fluent API и LINQ to SQL.

Ответ 2

Это очень просто с методами расширения Skip и Take.

var query = from i in ideas
            select i;

var paggedCollection = query.Skip(startIndex).Take(count);

Ответ 3

Я решил это немного иначе, чем то, что у других было, как я должен был сделать свой собственный paginator, с ретранслятором. Поэтому я сначала сделал набор номеров страниц для коллекции предметов, которые у меня есть:

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

Используя это, я могу легко разбить коллекцию элементов на коллекцию "страниц". Страница в этом случае представляет собой всего лишь набор элементов (IEnumerable<Item>). Так вы можете сделать это с помощью Skip и Take вместе с выбором индекса из pageRange, созданного выше:

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

Конечно, вы должны обрабатывать каждую страницу как дополнительную коллекцию, но, например, если вы вставляете повторителей, тогда на самом деле это легко обрабатывать.


Вариант TL;DR с одним слоем будет следующим:

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

Что можно использовать как это:

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

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

Ответ 4

Этот вопрос несколько старый, но я хотел опубликовать свой алгоритм подкачки, который показывает всю процедуру (включая взаимодействие с пользователем).

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

Однако, если вы после производительности и в производственном коде, мы все после производительности, вы не должны использовать пейджинг LINQ, как показано выше, а, скорее, базовый IEnumerator для непосредственного осуществления подкачки. На самом деле это так же просто, как и алгоритм LINQ, показанный выше, но более эффективный:

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())
        {
            int smallNumber = page.Current;
            Console.WriteLine(smallNumber);
        }

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

Объяснение: Недостатком использования Skip() в несколько раз "каскадным образом" является то, что он действительно не сохранит "указатель" итерации, где он был последним пропущен. - Вместо этого исходная последовательность будет загружена с помощью пропущенных вызовов, что приведет к повторному использованию всей последовательности. - Вы можете доказать это сами, когда создаете последовательность ideas, чтобы она давала побочные эффекты. → Даже если вы пропустили 10-20 и 20-30 и хотите обработать 40+, вы увидите, что все побочные эффекты 10-30 будут выполнены снова, прежде чем вы начнете повторять 40+. Вариант, использующий IEnumerable напрямую, вместо этого запомнит позицию конца последней логической страницы, поэтому нет явного пропуска и побочные эффекты не будут повторяться.