การเพจคอลเลกชันด้วย LINQ


คำตอบ:


43

ไม่กี่เดือนก่อนหน้านี้ฉันได้เขียนบล็อกโพสต์เกี่ยวกับ 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 หน้า: ท่อกรอง API คล่องแคล่วและ LINQ กับ SQL


64

มันง่ายมากด้วยวิธีการSkipและการTakeขยาย

var query = from i in ideas
            select i;

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

3
ฉันเชื่อว่ามันโอเคที่จะทำอะไรแบบนี้ เขาอาจจะมีคำตอบ แต่บางทีเขาก็อยากเห็นสิ่งที่คนอื่นคิดได้เช่นกัน
Outlaw Programmer

11
สิ่งนี้ถูกโพสต์ครั้งแรกในช่วงวันแรกของช่วงเบต้าของ StackOverflow ดังนั้นจึงเป็น 66 สำหรับรหัสบทความ ฉันกำลังทดสอบระบบสำหรับเจฟฟ์ นอกจากนี้ดูเหมือนว่าข้อมูลที่เป็นประโยชน์แทนที่จะเป็นอึทดสอบปกติที่บางครั้งมาจากการทดสอบเบต้า
Nick Berardi

14

ฉันแก้ไขสิ่งนี้ให้แตกต่างจากที่คนอื่นมีอยู่เล็กน้อยเพราะฉันต้องสร้างเลขหน้าของตัวเองด้วยทวน ดังนั้นฉันจึงรวบรวมหมายเลขหน้าสำหรับคอลเลกชันรายการที่ฉันมีก่อน

// 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และTakeร่วมกับการเลือกดัชนีจากที่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
    }
}

10

คำถามนี้ค่อนข้างเก่า แต่ฉันต้องการโพสต์อัลกอริธึมการเพจของฉันที่แสดงขั้นตอนทั้งหมด (รวมถึงการโต้ตอบกับผู้ใช้)

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+ คุณจะเห็นผลข้างเคียงทั้งหมดของ 10-30 ถูกดำเนินการอีกครั้งก่อนที่คุณจะเริ่มทำซ้ำ 40+ ตัวแปรที่ใช้IEnumerableอินเทอร์เฟซโดยตรงจะจำตำแหน่งของส่วนท้ายของหน้าตรรกะสุดท้ายแทนดังนั้นจึงไม่จำเป็นต้องข้ามอย่างชัดเจนและจะไม่เกิดผลข้างเคียงซ้ำ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.