使用PagedList进行分页,效率如何?

我一直在尝试实现分页已经有一段时间了,我发现这个教程用于使用MVC进行分页: ASP.NET MVC分页完美地完成了

现在,在此解决方案中,我在数据库中查询整个客户端集,然后返回客户端的分页列表而不是正常列表。

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

如果我理解错误的东西,请立即随意切断我,但对我而言,这个解决方案并不完美。

我误解了什么吗? 是否有一个更有效的解决方案或库,用于MVC的分页?

自然地,分页将需要知道总结果计数,以便逻辑确定有多少页等。但是,不是降低所有结果,而是将查询构建到数据库以返回分页量(例如30)和以及所有结果的计数。

例如,如果您使用的是Entity Framework或LINQ2SQL,则可以执行类似的操作

 IQueryable 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()时就完成了。

最后,我们最终得到的Object(ResultObject)可用于稍后进行分页。 由于我们有计数,我们已经知道我们在哪个页面(3我们跳过60,每页30)并且我们有结果显示。

进一步阅读和信息

如何:浏览查询结果

具有entity framework的服务器端分页

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() 

因此,您可以看到它已经使用推荐的服务器端分页方法skip和take,然后预先形成ToList()。

但是,如果您没有使用IQueryable,即IEnumerable,则使用以下代码:

 ///  /// Initializes a new instance of the  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. ///  /// The collection of objects to be divided into subsets. If the collection implements , it will be treated as such. /// The one-based index of the subset of objects to be contained by this instance. /// The maximum size of any individual subset. /// The specified index cannot be less than zero. /// The specified page size cannot be less than one. public PagedList(IEnumerable superset, int pageNumber, int pageSize) : this(superset.AsQueryable(), pageNumber, pageSize) { } 

问题是,根据用于获取IEnumerable的过滤可能包含所有记录,因此尽可能使用IQueryable以获得PagedList的最佳性能。

链接的教程看起来很奇怪,因为它使用List 。 这确实会将所有客户端带入内存,然后通过它进行分页。 相反,你应该寻找使用IQueryable ,特别是SkipTake ,所以分页应该是这样的

 IQueryable clients = repo.GetClients(); // lazy load - does nothing List paged = clients.Skip(20).Take(10).ToList(); // execute final SQL 

根据您使用的映射器,您将在EF,NHibernate,Linq-to-SQL等中找到类似的方法

您有三种方法可以在您的应用程序中实现分页:

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

如果你转到PagedList插件的github页面,你可以看到如果你有一个方法返回一个IQueryable那么PagedList魔术可以处理它而不返回数据库中的每一个项目。 如果您无法控制数据库中的查询返回给您,那么您必须依赖其他方法。

该页面的示例是这样的

 public class ProductController : Controller { public object Index(int? page) { var products = MyProductDataSource.FindAllProducts(); //returns IQueryable 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(); } } 

此组件(PagedList)适用于大量记录,第一次,每次选择页面时,它将对数据库进行2次调用。 一个返回记录数,另一个只返回所选页面的记录。 只需确保不要调用ToList()方法

  1. 使用.cshtml中的Webgrid对象编码,它会很好。
  2. 分页复杂性非常低。
  3. 干净的代码。
  4. Micro Soft BCL类。 少虫子。