用LINQ分页收集

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

几个月前,我写了一篇关于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代码库页面获取代码: pipe道,filter,Fluent API和LINQ to SQL 。

SkipTake扩展方法非常简单。

 var query = from i in ideas select i; var paggedCollection = query.Skip(startIndex).Take(count); 

我解决这个问题的方式与其他方式有所不同,因为我必须用自己的中继器来创build自己的分页器。 所以我首先为我收集的物品收集了一些页码:

 // 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和select从pageRange创build的pageRangeselect索引:

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

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

 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直接实现分页。 事实上,它与上面所示的LINQalgorithm一样简单,但性能更高:

 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()的缺点是,它不会真正存储迭代的“指针”,它最后被跳过。 相反,原始序列会被跳过调用,这将导致整个序列一遍又一遍地被消耗。 – 当你创build序列ideas时,你可以certificate自己,这样会产生副作用。 – >即使你已经跳过了10-20和20-30,并想要处理40+,你会看到10-30的所有副作用再次执行,然后再开始迭代40+。 直接使用IEnumerable的变体会记住最后一个逻辑页面结束的位置,所以不需要明确的跳过,副作用也不会重复。