Разбиение коллекции на страницы с помощью LINQ
Вопрос
Как просматривать коллекцию в LINQ, если у вас есть startIndex
и count
?
Решение
Несколько месяцев назад я написал сообщение в блоге о 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: Конвейеры, фильтры, Fluent API и LINQ to SQL.
Другие советы
Это очень просто с Skip
и Take
методы расширения.
var query = from i in ideas
select i;
var paggedCollection = query.Skip(startIndex).Take(count);
Я решил это немного иначе, чем другие, поскольку мне пришлось сделать свой собственный пагинатор с репитером.Итак, сначала я составил коллекцию номеров страниц для коллекции имеющихся у меня предметов:
// 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));
Конечно, вам придется обрабатывать каждую страницу как дополнительную коллекцию, но, например.если вы вкладываете повторители, с этим действительно легко справиться.
А однострочный TLDR версия будет такой:
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
}
}
Этот вопрос несколько устарел, но я хотел опубликовать свой алгоритм пейджинга, который показывает всю процедуру (включая взаимодействие с пользователем).
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())
{
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);
}
Объяснение:Недостаток использования Skip()
несколько раз «каскадным образом» заключается в том, что он на самом деле не сохраняет «указатель» итерации, где он был пропущен в последний раз.- Вместо этого исходная последовательность будет предварительно загружена вызовами пропуска, что приведет к «потреблению» уже «израсходованных» страниц снова и снова.- Вы можете доказать это сами, когда создадите последовательность ideas
так что это дает побочные эффекты.-> Даже если вы пропустили 10-20 и 20-30 и хотите обработать 40+, вы увидите, что все побочные эффекты 10-30 выполняются снова, прежде чем начать итерацию 40+.Вариант с использованием IEnumerable
напрямую, вместо этого будет запоминаться положение конца последней логической страницы, поэтому явный пропуск не требуется и побочные эффекты не будут повторяться.