ページ区切り
改ページとは、結果を一度にすべてではなく、ページ単位で取得することを指します。これは通常、大きな結果セットに対して行われます。この場合、ユーザー インターフェイスが表示され、ユーザーは結果の次のページまたは前のページに移動できます。
警告
使用する改ページの方法に関係なく、常に順序が完全に一意になるようにしてしてください。 たとえば、結果が日付によってのみ並べ替えられるが、同じ日付で複数の結果が存在する可能性がある場合、改ページによって結果が 2 つの改ページ クエリ間で異なる順序で並べ替えられるため、スキップされる可能性があります。 日付と ID (またはその他の一意のプロパティ、またはプロパティの組み合わせ) の両方で並べ替えると、順序が完全に一意になるため、この問題を回避できます。 リレーショナル データベースでは、主キーであっても、既定で並べ替えは適用されないことに注意してください。
Note
Azure Cosmos DB にはページネーションのための独自のメカニズムがあります。専用のドキュメント ページをご覧ください。
オフセットの改ページ
データベースで改ページ位置の自動修正を実装する一般的な方法は、Skip
および Take
LINQ 演算子 (SQL では OFFSET
および LIMIT
) を使用する方法です。 10 件の結果のページ サイズの場合、次のようにして 3 ページ目を EF Core でフェッチできます。
var position = 20;
var nextPage = context.Posts
.OrderBy(b => b.PostId)
.Skip(position)
.Take(10)
.ToList();
残念ながら、この手法は非常に直感的ですが、いくつかの重大な欠点もあります。
- エントリがアプリケーションに返されない場合でも、その最初の 20 件をデータベースで処理する必要があります。これにより、スキップされる行が増えるたびに計算負荷が高まる可能性があります。
- 更新が同時に行われると、改ページによって特定のエントリがスキップされたり、2 回表示されたりする可能性があります。 たとえば、ユーザーが 2 ページから 3 ページに移動する際にエントリが削除されると、結果セット全体が "上に移動" し、1 件のエントリがスキップされます。
キーセットの改ページ
オフセットベースの改ページ (キーセットの改ページまたはシークベースの改ページとも呼ばれる) の代わりに、単に WHERE
句を使用してオフセットの代わりに行をスキップすることをお勧めします。 この場合、(オフセットの代わりに) フェッチされた最後のエントリの関連する値を記憶し、その行の後に次の行を要求する必要があります。 たとえば、フェッチした最後のページの最後のエントリの ID 値が 55 であると仮定する場合、単に次の操作を行います。
var lastId = 55;
var nextPage = context.Posts
.OrderBy(b => b.PostId)
.Where(b => b.PostId > lastId)
.Take(10)
.ToList();
PostId
でインデックスが定義されていると仮定すると、このクエリは非常に効率的であり、低い ID 値で同時に発生する変更にも影響を受けません。
キーセットの改ページは、ユーザーが前後に移動する改ページ インターフェイスには適していますが、ユーザーが特定のページにジャンプできるランダム アクセスはサポートしていません。 ランダム アクセスの改ページを設定するには、上で説明したようにオフセットの改ページを使用する必要があります。オフセットの改ページには欠点があるため、ユース ケースにランダム アクセスの改ページが本当に必要かどうか、前後のページ ナビゲーションで十分かどうかを慎重に検討してください。 ランダム アクセスの改ページが必要な場合、堅牢な実装では、前後のページに移動するときにキーセットの改ページを使用し、それ以外のページにジャンプするときにオフセット ナビゲーションを使用できます。
複数の改ページ キー
キーセットの改ページを使用する場合、多くの場合、複数のプロパティで並べ替える必要があります。 たとえば、次のクエリは日付と ID で改ページされます。
var lastDate = new DateTime(2020, 1, 1);
var lastId = 55;
var nextPage = context.Posts
.OrderBy(b => b.Date)
.ThenBy(b => b.PostId)
.Where(b => b.Date > lastDate || (b.Date == lastDate && b.PostId > lastId))
.Take(10)
.ToList();
これにより、次のページは前のページが終了した場所から正確に始まります。 さらに順序付けキーを追加するときに、追加の句を追加できます。
Note
ほとんどの SQL データベースでは、行の値 WHERE (Date, Id) > (@lastDate, @lastId)
を使用した、上記のよりシンプルで効率的なバージョンをサポートしています。 EF Core では現在、これを LINQ クエリで表現することはサポートされていません。これは、#26822 で追跡されています。
インデックス
他のクエリと同様、パフォーマンスを向上させるには適切なインデックス作成が不可欠です。改ページの順序に対応するインデックスを設定してください。 複数の列で並べ替える場合は、それらの複数の列に対するインデックスを定義できます。これは、複合インデックスと呼ばれます。
詳細については、「インデックス」のドキュメントのページを参照してください。
その他のリソース
- オフセットベースの改ページの欠点とキーセットの改ページの詳細については、こちらの投稿を参照してください。
- .NET Data Community Standup セッションでは、ページ分割について説明し、上記のすべての概念についてデモを行っています。
- 技術的に詳しいプレゼンテーションでは、オフセットとキーセットの改ページを比較しています。 このコンテンツは PostgreSQL データベースを扱っていますが、一般的な情報は他のリレーショナル データベースでも有効です。
- キーセットの改ページを簡略化する EF Core 上の拡張機能については、「MR.EntityFrameworkCore.KeysetPagination」と「MR.AspNetCore.Pagination」を参照してください。
.NET