使用LINQ对集合进行分页

89

如果你有一个startIndex和一个count,在LINQ中如何分页遍历集合?

4个回答

65

使用SkipTake扩展方法非常简单。

var query = from i in ideas
            select i;

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

4
我相信这样做没问题。他可能有答案,但也许他想看看其他人能提出什么。 - Tim Frey
13
这篇文章最初发布于StackOverflow的beta测试期间的第一天,因此文章ID为66。我当时是在为Jeff测试系统。此外,这似乎是有用信息,而不是通常在beta测试中出现的无用测试内容。 - Nick Berardi

45
几个月前,我写了一篇关于流畅接口和LINQ的博客文章,其中使用了一个扩展方法来扩展IQueryable<T>和另一个类,以提供以下自然的方法来分页LINQ集合。
var query = from i in ideas
            select i;
var pagedCollection = query.InPagesOf(10);
var pageOfIdeas = pagedCollection.Page(2);
你可以从MSDN Code Gallery页面获取代码:Pipelines,Filters,Fluent API和LINQ to SQL

15

我解决这个问题的方法与其他人略有不同,因为我不得不自己制作一个带有重复器的分页器。因此,我首先为我的项目集合创建了一组页面编号:

// 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>)。以下是如何使用SkipTake结合上面创建的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
    }
}

11

这个问题有点老了,但我想发表我的分页算法,展示整个过程(包括用户交互)。

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()的缺点是,它不会真正存储迭代的“指针”,即最后跳过的位置。相反,原始序列将用Skip调用前置,这将导致反复“消耗”已经被“消耗”的页面。当你创建一个产生副作用的序列ideas时,你可以证明这一点。即使你跳过了10-20和20-30并想处理40+,你仍然会看到10-30的所有副作用再次执行,然后才开始迭代40+。而直接使用IEnumerable接口的变体将记住上一页的末尾位置,因此不需要显式跳过,并且不会重复副作用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接