web-dev-qa-db-ja.com

PagedListでのページング、それは効率的ですか?

私はかなり長い間ページングを実装しようとしていましたが、MVCでページングするためのこのチュートリアルを見つけました: ASP.NET MVC Paging Done Peronely

今、このソリューションでは、クライアントに全体のセットをデータベースに照会し、通常のリストの代わりにクライアントのページ化されたリストを返します。

ページあたり10または20のエントリのみを表示する予定であり、私のデータベースには100万以上のエントリが簡単に存在するため、これは不愉快なことです。したがって、Indexページを表示するたびにデータベース全体をクエリすることは、せいぜい不十分な解決策のようです。

私が何か間違ったことを理解している場合は、今すぐ遠慮なく私を切ってください。しかし、私にとってこの解決策は完璧ではありません。

私は何かを誤解しましたか? MVCによるページ分割のためのより効率的なソリューションまたはライブラリはありますか?

17
Flame_Phoenix

当然、ページングでは、ロジックがページ数などを判断するために、結果の総数を知る必要があります。ただし、すべての結果を下げるのではなく、データベースにクエリを作成して、ページングされた量(たとえば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)、表示する結果があります。

さらに読むと情報

方法:クエリ結果のページ

エンティティフレームを使用したサーバー側のページング

9
JonE

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を使用することです。

4
Paul Zahra

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();
    }
}
2
Loofer

リンクされたチュートリアルは、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などで同様のメソッドが見つかります

2
oleksii

アプリケーションでページネーションを実装するには、3つの方法があります。

  • Repositoryがクライアントに最小量のデータでDTOを返すように強制し、ページネーションを提供するjqueryプラグインのいくつかを使用します。これは簡単な方法ですが、(あなたの場合のように)時々これはオプションではありません。したがって、サーバー側のページネーションを実装する必要があります
  • コレクション全体をキャッシュし、LINQ拡張子が付いた必要なページを返します。私は、リポジトリとORMの多くがこれを内部で実行していると思います(Entity Frameworkでは機能しませんでした。はっきりとは言えません)。このソリューションの問題は、キャッシュとデータベースを同期する必要があり、すべてのデータを格納する(またはクラウドなどに)十分なメモリを備えたサーバーを取得する必要があることです。他の回答と同様に、遅延IEnumerable作業で不要なデータをスキップできるため、コレクションをキャッシュする必要はありません。
  • DB側でページネーションを実装します。 SQLを使用している場合は、ROW_NUMBER構成を使用できます。これは MS SQL または Oracle または MySQL (実際にはROW_NUMBER自体ではなく、アナログのみ)。 NoSQLソリューションがある場合は、ドキュメントを確認する必要があります。
1
VMAtm

このコンポーネント(PagedList)は、多数のレコードに対して最初から正常に機能し、ページを選択するたびに、データベースに対して2つの呼び出しを行います。 1つはレコード数を返し、もう1つは選択したページのレコードのみを返します。 ToList()メソッドを呼び出さないことを確認してください

0
foluis