私はかなり長い間ページングを実装しようとしていましたが、MVCでページングするためのこのチュートリアルを見つけました: ASP.NET MVC Paging Done Peronely
今、このソリューションでは、クライアントに全体のセットをデータベースに照会し、通常のリストの代わりにクライアントのページ化されたリストを返します。
ページあたり10または20のエントリのみを表示する予定であり、私のデータベースには100万以上のエントリが簡単に存在するため、これは不愉快なことです。したがって、Index
ページを表示するたびにデータベース全体をクエリすることは、せいぜい不十分な解決策のようです。
私が何か間違ったことを理解している場合は、今すぐ遠慮なく私を切ってください。しかし、私にとってこの解決策は完璧ではありません。
私は何かを誤解しましたか? MVCによるページ分割のためのより効率的なソリューションまたはライブラリはありますか?
当然、ページングでは、ロジックがページ数などを判断するために、結果の総数を知る必要があります。ただし、すべての結果を下げるのではなく、データベースにクエリを作成して、ページングされた量(たとえば30)を返すだけです。すべての結果の数だけでなく。
たとえば、Entity FrameworkまたはLINQ2SQLを使用している場合、次のようなことができます
IQueryable<Result> allResults = MyRepository.RetrieveAll();
var resultGroup = allResults.OrderByDescending(r => r.DatePosted)
.Skip(60)
.Take(30)
.GroupBy(p => new {Total = allResults.Count()})
.First();
var results = new ResultObject
{
ResultCount = resultGroup.Key.Total,
Results = resultGrouping.Select(r => r)
};
必要なものを確定するまで結果セットで.ToList()を実行していないため、結果をメモリに取り込みません。これは、結果セットで.First()を呼び出すときに行われます。
最後に、(ResultObject)で終わるオブジェクトを使用して、後でページングを行うことができます。カウントがあるので、現在どのページにいるかがわかっており(3は60をスキップしたため、1ページあたり30)、表示する結果があります。
さらに読むと情報
Githubの例は、IQueryableを使用していることを示しています。これは、ToPagedList()によって使用されます。これは、コードがかなり最適化されており、それ自体はすべてのレコードを返さないことを意味します...
クラスPagedListのコードを見る
// superset is the IQueryable.
TotalItemCount = superset == null ? 0 : superset.Count();
// add items to internal list
if (superset != null && TotalItemCount > 0)
Subset.AddRange(pageNumber == 1
? superset.Skip(0).Take(pageSize).ToList()
: superset.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToList()
ご覧のとおり、サーバー側の推奨ページングメソッドであるスキップとテイクを既に使用しており、ToList()をプリフォームしています。
ただし、IQueryable、つまりIEnumerableを使用していない場合は、次のコードが使用されます。
/// <summary>
/// Initializes a new instance of the <see cref="PagedList{T}"/> class that divides the supplied superset into subsets the size of the supplied pageSize. The instance then only containes the objects contained in the subset specified by index.
/// </summary>
/// <param name="superset">The collection of objects to be divided into subsets. If the collection implements <see cref="IQueryable{T}"/>, it will be treated as such.</param>
/// <param name="pageNumber">The one-based index of the subset of objects to be contained by this instance.</param>
/// <param name="pageSize">The maximum size of any individual subset.</param>
/// <exception cref="ArgumentOutOfRangeException">The specified index cannot be less than zero.</exception>
/// <exception cref="ArgumentOutOfRangeException">The specified page size cannot be less than one.</exception>
public PagedList(IEnumerable<T> superset, int pageNumber, int pageSize)
: this(superset.AsQueryable<T>(), pageNumber, pageSize)
{
}
問題は、最初にIEnumerableを取得するために使用されるフィルタリングに応じて、すべてのレコードを含めることができるため、PagedListのパフォーマンスを最適化するために、可能な場合はIQueryableを使用することです。
PagedList
addon のgithubページに移動すると、IQueryable<T>
を返すメソッドがある場合、PagedListマジックがすべてを返すことなく機能することがわかりますデータベースのアイテム。データベースからのクエリが返すものを制御できない場合は、他の方法を使用する必要があります。
そのページの例はこれです
public class ProductController : Controller
{
public object Index(int? page)
{
var products = MyProductDataSource.FindAllProducts(); //returns IQueryable<Product> representing an unknown number of products. a thousand maybe?
var pageNumber = page ?? 1; // if no page was specified in the querystring, default to the first page (1)
var onePageOfProducts = products.ToPagedList(pageNumber, 25); // will only contain 25 products max because of the pageSize
ViewBag.OnePageOfProducts = onePageOfProducts;
return View();
}
}
リンクされたチュートリアルは、List<Client>
を使用しているため、奇妙に見えます。これにより、実際にすべてのクライアントがメモリに読み込まれ、ページ送りされます。代わりに、IQueryable<T>
、具体的にはSkip
およびTake
を使用するメソッドを探す必要があるため、ページングは次のようになります。
IQueryable<Client> clients = repo.GetClients(); // lazy load - does nothing
List<Client> paged = clients.Skip(20).Take(10).ToList(); // execute final SQL
使用するマッパーに応じて、EF、NHibernate、Linq-to-SQLなどで同様のメソッドが見つかります
アプリケーションでページネーションを実装するには、3つの方法があります。
Repository
がクライアントに最小量のデータでDTOを返すように強制し、ページネーションを提供するjquery
プラグインのいくつかを使用します。これは簡単な方法ですが、(あなたの場合のように)時々これはオプションではありません。したがって、サーバー側のページネーションを実装する必要がありますLINQ
拡張子が付いた必要なページを返します。私は、リポジトリとORMの多くがこれを内部で実行していると思います(Entity Framework
では機能しませんでした。はっきりとは言えません)。このソリューションの問題は、キャッシュとデータベースを同期する必要があり、すべてのデータを格納する(またはクラウドなどに)十分なメモリを備えたサーバーを取得する必要があることです。他の回答と同様に、遅延IEnumerable
作業で不要なデータをスキップできるため、コレクションをキャッシュする必要はありません。SQL
を使用している場合は、ROW_NUMBER
構成を使用できます。これは MS SQL または Oracle または MySQL (実際にはROW_NUMBER
自体ではなく、アナログのみ)。 NoSQL
ソリューションがある場合は、ドキュメントを確認する必要があります。このコンポーネント(PagedList)は、多数のレコードに対して最初から正常に機能し、ページを選択するたびに、データベースに対して2つの呼び出しを行います。 1つはレコード数を返し、もう1つは選択したページのレコードのみを返します。 ToList()メソッドを呼び出さないことを確認してください