Làm thế nào để bạn duyệt qua một bộ sưu tập trong LINQ cho rằng bạn có một startIndex
và một count
?
Làm thế nào để bạn duyệt qua một bộ sưu tập trong LINQ cho rằng bạn có một startIndex
và một count
?
Câu trả lời:
Một vài tháng trước, tôi đã viết một bài đăng trên blog về Giao diện thông thạo và LINQ, sử dụng Phương thức mở rộng trên IQueryable<T>
và một lớp khác để cung cấp cách phân trang tự nhiên sau đây cho bộ sưu tập LINQ.
var query = from i in ideas
select i;
var pagedCollection = query.InPagesOf(10);
var pageOfIdeas = pagedCollection.Page(2);
Bạn có thể lấy mã từ Trang Thư viện Mã MSDN: Đường ống, Bộ lọc, API thông thạo và LINQ to SQL .
Nó rất đơn giản với các phương thức mở rộng Skip
và Take
.
var query = from i in ideas
select i;
var paggedCollection = query.Skip(startIndex).Take(count);
Tôi đã giải quyết vấn đề này hơi khác so với những gì những người khác có vì tôi phải tạo bộ phân trang của riêng mình, với một bộ lặp. Vì vậy, trước tiên tôi đã tạo một bộ sưu tập các số trang cho bộ sưu tập các mục mà tôi có:
// 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]
Bằng cách sử dụng này, tôi có thể dễ dàng phân vùng bộ sưu tập mục thành một bộ sưu tập các "trang". Một trang trong trường hợp này chỉ là một tập hợp các mục ( IEnumerable<Item>
). Đây là cách bạn có thể thực hiện bằng cách sử dụng Skip
và Take
cùng với việc chọn chỉ mục từ phần pageRange
đã tạo ở trên:
IEnumerable<IEnumerable<Item>> pageRange
.Select((page, index) =>
myItems
.Skip(index*PageSize)
.Take(PageSize));
Tất nhiên bạn phải xử lý từng trang như một bộ sưu tập bổ sung nhưng ví dụ: nếu bạn đang lồng các bộ lặp thì điều này thực sự dễ dàng xử lý.
Các one-liner TLDR phiên bản sẽ là:
var pages = Enumerable
.Range(0, pageCount)
.Select((index) => myItems.Skip(index*PageSize).Take(PageSize));
Có thể được sử dụng như sau:
for (Enumerable<Item> page : pages)
{
// handle page
for (Item item : page)
{
// handle item in page
}
}
Câu hỏi này hơi cũ, nhưng tôi muốn đăng thuật toán phân trang của mình để hiển thị toàn bộ quy trình (bao gồm cả tương tác người dùng).
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);
Tuy nhiên, nếu bạn đang theo đuổi hiệu suất và trong mã sản xuất, tất cả chúng ta đều theo đuổi hiệu suất, bạn không nên sử dụng phân trang của LINQ như được hiển thị ở trên, mà là cơ bản IEnumerator
để tự thực hiện phân trang. Trên thực tế, nó đơn giản như thuật toán LINQ được hiển thị ở trên, nhưng hiệu quả hơn:
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);
}
Giải thích: Nhược điểm của việc sử dụng Skip()
nhiều lần theo "cách xếp tầng" là nó sẽ không thực sự lưu trữ "con trỏ" của lần lặp, nơi nó bị bỏ qua lần cuối. - Thay vào đó, trình tự ban đầu sẽ được tải trước với các lệnh gọi bỏ qua, điều này sẽ dẫn đến việc "tiêu thụ" các trang đã được "tiêu thụ" nhiều lần. - Bạn có thể chứng minh rằng chính mình, khi bạn tạo trình tự ideas
để nó mang lại tác dụng phụ. -> Ngay cả khi bạn đã bỏ qua 10-20 và 20-30 và muốn xử lý 40+, bạn sẽ thấy tất cả các tác dụng phụ của 10-30 được thực thi lại, trước khi bạn bắt đầu lặp lại 40+. Thay vào đó, biến thể sử dụng IEnumerable
giao diện của trực tiếp sẽ ghi nhớ vị trí của phần cuối của trang logic cuối cùng, vì vậy không cần bỏ qua rõ ràng và các tác dụng phụ sẽ không lặp lại.