我一直在尝试实现分页已经有一段时间了,我发现本教程用于使用MVC进行分页:ASP.NET MVC Paging Done Perfectly
现在,在此解决方案中,我在数据库中查询整个客户端,然后返回客户端的分页列表而不是普通列表。
我发现这令人不安,因为我只计划每页显示10或20个条目,而我的数据库很容易就会有超过一百万个。因此,每次我想要显示Index
页面时查询整个数据库似乎都是一个不好的解决方案。
如果我理解错误的话,请立即给我剪掉,但对我而言,这个解决方案绝非完美。
我误解了什么吗?是否有更有效的解决方案或库可以用于MVC的分页?
答案 0 :(得分:7)
自然地,分页将需要知道总结果计数,以便逻辑确定有多少页面等。但是,不是将所有结果都降低,而是将查询构建到数据库以返回分页量(例如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()时完成的。
最后,我们最终得到的Object(ResultObject)可用于稍后进行分页。由于我们有计数,我们已经知道我们在哪个页面(3我们跳过60,每页30)并且我们有结果显示。
进一步阅读和信息
答案 1 :(得分:4)
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,则使用以下代码:
/// <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的最佳性能。
答案 2 :(得分:2)
如果你转到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();
}
}
答案 3 :(得分:2)
链接教程看起来很奇怪,因为它使用的是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等中找到类似的方法
答案 4 :(得分:1)
您可以通过三种方式在应用程序中实现分页:
Repository
以最少量的数据将DTO返回给客户端,并使用一些jquery
插件自行提供分页。这是一种简单的方法,但有时(如你的情况),这不是一个选项。所以你必须实现服务器端分页LINQ
扩展名的所需页面。我认为许多存储库和ORM在内部都这样做(不能与Entity Framework
一起使用,不能肯定地说)。此解决方案的问题在于您必须同步缓存和数据库,并且您必须使服务器具有足够的内存来存储您的所有数据(或转到云或其他内容)。与其他答案一样,您可以使用惰性IEnumerable
工作跳过不需要的数据,因此您不需要缓存该集合。SQL
,则可以使用ROW_NUMBER
构建,它可以在MS SQL或Oracle或MySQL中使用(不是ROW_NUMBER
实际上,只有模拟)。如果您有NoSQL
解决方案,那么您必须检查文档。答案 5 :(得分:0)
此组件(PagedList)适用于大量记录,第一次和每次选择页面时,它将对数据库进行2次调用。一个返回记录数,另一个只返回所选页面的记录。只要确保你没有调用ToList()方法
答案 6 :(得分:-1)