用PagedList分页,效率高吗?

Paging with PagedList, is it efficient?

我尝试实现分页已经有一段时间了,我发现了这个使用 MVC 进行分页的教程:ASP.NET MVC Paging Done Perfectly

现在,在此解决方案中,我在数据库中查询 整组客户 ,然后我 return 分页的客户列表而不是普通列表。

我觉得这很烦人,因为我只打算每页显示 10 或 20 个条目,而我的数据库很容易就会有超过一百万个条目。因此,每次我想显示 Index 页面时查询整个数据库似乎充其量是一个糟糕的解决方案。

如果我理解有误,请随时打扰我,但对我来说这个解决方案一点也不完美。

我是不是误会了什么?是否有更有效的解决方案或库可用于 MVC 分页?

自然地,分页将需要了解总结果数,以便逻辑确定有多少页等。但是,与其降低所有结果,不如将查询构建到数据库 return分页数量(例如 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,每页 30)并且我们有结果要显示。

进一步阅读和信息

How To: Page through Query Results

Server Side Paging with Entity Frame

您可以通过三种方式在您的应用程序中实现分页:

  • 强制您的 Repository 将数据量最少的 DTO return 返回给客户端,并使用一些 jquery 插件自己提供分页。这是一种简单的方法,但有时(如您的情况)这不是一种选择。所以你必须实现服务器端分页
  • 缓存整个集合,return 需要的页面 LINQ 扩展。我认为许多存储库和 ORM 在内部执行此操作(不适用于 Entity Framework,不能肯定)。这个解决方案的问题是你必须同步缓存和数据库,你必须让服务器有足够的内存来存储你的所有数据(或者去云,或其他东西)。与其他答案一样,您可以使用惰性 IEnumerable 工作跳过不需要的数据,因此您不需要缓存集合..
  • 在数据库端实现分页。如果你正在使用 SQL,你可以使用 ROW_NUMBER 结构,它可以在 MS SQL or in Oracle or in MySQL 中工作(实际上不是 ROW_NUMBER 本身,只是模拟)。如果您有 NoSQL 解决方案,那么您必须检查它的文档。

如果您转到 PagedList addon 的 github 页面,您会看到如果您有一个返回 IQueryable<T> 的方法,那么 PagedList 魔法可以处理它而无需返回每个来自数据库的项目。如果您无法控制从数据库 returns 向您查询的内容,那么您必须依赖其他方法。

该页面的示例是这样的

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> 的方法,特别是 SkipTake,因此分页应该看起来像

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 等中找到类似的方法

github 上的示例说明它正在使用一个 IQueryable,然后由 ToPagedList() 使用,这意味着代码已经非常优化并且本身不会 return 所有记录。 ..

查看class 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()

如您所见,它已经使用了推荐的服务器端分页方法 skip 和 take,然后执行 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 的筛选可能包含所有记录,因此尽可能使用 IQueryable 以获得 PagedList 的最佳性能。

此组件 (PagedList) 可以很好地处理大量记录,第一次和每次 select 一个页面都会对数据库进行 2 次调用。一个 return 记录的数量,另一个 return 只 select 页面的记录。只要确保您不调用 ToList() 方法

  1. 在 .cshtml 中使用 Webgrid 对象编写代码 & 它会很好。
  2. 分页复杂度会很低。
  3. 干净的代码。
  4. 微软 BCL class。错误更少。

如果您有一些模块或服务,并且您的分页在服务器端,或者可能是自定义分页,您可以在asp.net中使用这段代码核心

在控制器中:

  var result = Get paginated result from your service;
  ViewData["YourList"] = new StaticPagedList<YourListType>(result.Entity, result.Index, result.Size, result.Total);

在剃须刀页面中:

var list = ViewData["YourList"] as IPagedList<YourListType>;