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コードギャラリーページから入手できます:パイプライン、フィルター、Fluent API、およびLINQ toSQL


64

SkipおよびTake拡張メソッドを使用すると非常に簡単です。

var query = from i in ideas
            select i;

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

3
このようなことをしても大丈夫だと思います。彼は答えがあるかもしれませんが、他の人が何を思い付くことができるかを見たいのかもしれません。
アウトロープログラマー

11
これは元々、StackOverflowのベータ期間の初日に投稿されたため、記事IDは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>)。これは、上で作成したものからインデックスを選択すること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
    }
}

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 +を処理したい場合でも、40 +の反復を開始する前に、10-30のすべての副作用が再度実行されるのがわかります。IEnumerableのインターフェイスを直接使用するバリアントは、代わりに最後の論理ページの終わりの位置を記憶するため、明示的なスキップは不要であり、副作用は繰り返されません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.