Phân trang một bộ sưu tập với LINQ


84

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 startIndexvà một count?

Câu trả lời:


43

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 .


64

Nó rất đơn giản với các phương thức mở rộng SkipTake.

var query = from i in ideas
            select i;

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

3
Tôi tin rằng việc làm như thế này là OK. Anh ấy có thể có câu trả lời nhưng có thể anh ấy cũng muốn xem những gì người khác có thể đưa ra.
Lập trình viên ngoài vòng pháp luật,

11
Điều này ban đầu được đăng trong ngày đầu tiên của giai đoạn beta của StackOverflow, do đó là 66 cho ID bài viết. Tôi đang thử nghiệm hệ thống cho Jeff. Thêm vào đó, nó dường như là thông tin hữu ích thay vì thử nghiệm thông thường mà đôi khi xuất hiện từ thử nghiệm beta.
Nick Berardi,

14

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

10

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 IEnumerablegiao 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.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.