76


5

LINQを使ってコレクションをページングする

startIndex`と count`があるとすると、LINQのコレクションをどのようにページ送りしますか?

4 Answer


62


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

var query = from i in ideas
            select i;

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


39


数ヶ月前、私はFluent InterfacesとLINQに関するブログ記事を書きました。それは `IQueryable`の拡張メソッドと、LINQコレクションをページ分割する次の自然な方法を提供するためのクラスを使いました。

var query = from i in ideas
            select i;
var pagedCollection = query.InPagesOf(10);
var pageOfIdeas = pagedCollection.Page(2);

MSDNコードギャラリーページからコードを入手できます。http://code.msdn.microsoft.com/productschallenge [パイプライン、フィルタ、Fluent API、およびLINQ to SQL]。


12


私はリピーターを使って、私自身のページネーターを作らなければならなかったので他の人が持っているものとは少し違った方法でこれを解決しました。 それで私は最初に私が持っているアイテムのコレクションのためにページ番号のコレクションを作りました:

// assumes that the item collection is "myItems"

int pageCount = (myItems.Count + PageSize - 1) / PageSize;

IEnumerable pageRange = Enumerable.Range(1, pageCount);
   // pageRange contains [1, 2, ... , pageCount]

これを使用して、私はアイテムコレクションを「ページ」のコレクションに簡単に分割することができました。 この場合のページは単なるアイテムのコレクションです( IEnumerable)。 これは、上で作成した pageRange`からインデックスを選択しながら Skip`と `Take`を使ってそれを行う方法です:

IEnumerable> pageRange
    .Select((page, index) =>
        myItems
            .Skip(index*PageSize)
            .Take(PageSize));

もちろん、各ページを追加のコレクションとして扱う必要があります。 あなたがリピーターを入れ子にしているなら、これは実際に扱うのが簡単です。

'' '' '

_one-liner TLDR_バージョンはこれになります。

var pages = Enumerable
    .Range(0, pageCount)
    .Select((index) => myItems.Skip(index*PageSize).Take(PageSize));

これとして使用することができます:

for (Enumerable page : pages)
{
    // handle page

    for (Item item : page)
    {
        // handle item in page
    }
}


9


この質問はやや古くなっていますが、私は全手順(ユーザーの操作を含む)を示すページングアルゴリズムを投稿したいと思いました。

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()」を複数回使用することの欠点は、最後にスキップされた反復の「ポインタ」を実際には格納しないことです。 - 代わりに、元のシーケンスがスキップ呼び出しでフロントロードされます。これは、すでに「消費された」ページを何度も「消費する」ことにつながります。 - シーケンス「アイデア」を作成して副作用が生じるようにすると、自分でそれを証明できます。 - > 10-20と20-30をスキップして40を処理したい場合でも、40の繰り返しを始める前に、10-30のすべての副作用が再度実行されるのがわかります。 `IEnumerable`のインターフェースを直接使ったバリアントは、代わりに最後の論理ページの終わりの位置を覚えているので、明示的なスキップは不要で、副作用は繰り返されません。