使用 LINQ 对集合进行分页

Posted

技术标签:

【中文标题】使用 LINQ 对集合进行分页【英文标题】:Paging a collection with LINQ 【发布时间】:2010-09-05 05:32:24 【问题描述】:

如果你有一个startIndex 和一个count,你如何在 LINQ 中对集合进行分页?

【问题讨论】:

【参考方案1】:

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

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+,在开始迭代 40+ 之前,您会看到 10-30 的所有副作用再次执行。 直接使用IEnumerable的接口的变种,会记住最后一个逻辑页面的结束位置,所以不需要显式跳过,也不会重复副作用。

【讨论】:

【参考方案2】:

几个月前,我写了一篇关于 Fluent Interfaces 和 LINQ 的博文,其中使用了 IQueryable&lt;T&gt; 上的扩展方法和另一个类来提供以下对 LINQ 集合进行分页的自然方式。

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

您可以从 MSDN 代码库页面获取代码:Pipelines, Filters, Fluent API and LINQ to SQL。

【讨论】:

【参考方案3】:

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

var query = from i in ideas
            select i;

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

【讨论】:

我相信做这样的事情是可以的。他可能有答案,但也许他也想看看其他人能想出什么。 这最初是在 *** 测试期的第一天发布的,因此文章 ID 为 66。我正在为 Jeff 测试系统。此外,它似乎是有用的信息,而不是有时来自 beta 测试的常见测试废话。【参考方案4】:

我解决这个问题的方式与其他人的解决方案有点不同,因为我必须使用中继器制作自己的分页器。所以我首先为我拥有的项目集合制作了一个页码集合:

// 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&lt;Item&gt;)。这是您可以使用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
    

【讨论】:

以上是关于使用 LINQ 对集合进行分页的主要内容,如果未能解决你的问题,请参考以下文章

2017-6-2 Linq 高级查询 (分页和组合查)集合取交集

如何在 Laravel 5 中对合并的集合进行分页?

我如何对 laravel 集合进行分页?

C#规范整理·集合和Linq

Laravel 5.3 对 sortBy 集合进行分页

为啥我不能对 Visio Masters 集合使用 Linq 查询?