The RavenDB LINQ client supports both Skip and Take functions, making paging through a collection super easy:
int pageSize = 10;
int pageIndex = 2;
var candidates = session.Query<Candidate>()
.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
.ToList();
Note: The page number requested by the user is normally 1-based, hence the need to do pageIndex - 1.
To add paging to your UI you’ll typically need the following information:
- Page index
- Page size
- Total number of items
- Total Pages
- Whether there is a previous page
- Whether there is a next page
Most of the above can be calculated from the total number of query results, which RavenDB conveniently provides via the RavenQueryStatistics
object.
RavenQueryStatistics stats;
var candidates = session.Query<Candidate>()
.Statistics(out stats)
.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
.ToList();
var totalNumberOfItems = stats.TotalResults;
To encapsulate the above we use the following “PagedList” implementation. In addition to automatically calculating the total number of records (issuing a Count()
on the input query/collection) you can also specify it manually, perfect for both NHibernate future queries and of course RavenDB.
public interface IPagedList : IEnumerable
{
int PageIndex { get; }
int PageSize { get; }
int TotalCount { get; }
int TotalPages { get; }
bool HasPreviousPage { get; }
bool HasNextPage { get; }
}
public interface IPagedList<T> : IPagedList, IList<T>
{
}
public class PagedList<T> : List<T>, IPagedList<T>
{
public PagedList(IEnumerable<T> source, int pageIndex, int pageSize) :
this(source.GetPage(pageIndex, pageSize), pageIndex, pageSize, source.Count()) { }
public PagedList(IEnumerable<T> source, int pageIndex, int pageSize, int totalCount)
{
this.TotalCount = totalCount;
this.TotalPages = totalCount / pageSize;
if (totalCount % pageSize > 0)
TotalPages++;
this.PageSize = pageSize;
this.PageIndex = pageIndex;
this.AddRange(source.ToList());
}
public int PageIndex { get; private set; }
public int PageSize { get; private set; }
public int TotalCount { get; private set; }
public int TotalPages { get; private set; }
public bool HasPreviousPage { get { return (PageIndex > 0); } }
public bool HasNextPage { get { return (PageIndex + 1 < TotalPages); } }
}
Let’s look at how we use this in our ASP.NET MVC controllers:
[HttpGet]
public ViewResult Index(int page = 1)
{
int pageSize = 20;
RavenQueryStatistics stats;
var candidates = session.Query<Fabrik.Recruit.Domain.Candidate>()
.Statistics(out stats)
.OrderBy(c => c.ContactDetails.LastName)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(c => new CandidatesIndexModel.CandidateContactCard
{
Id = c.Id,
Name = c.ContactDetails.FullName,
})
.ToList();
var model = new PagedList<CandidatesIndexModel.CandidateContactCard>(
candidates,
page - 1,
pageSize,
stats.TotalResults
);
return View(model);
}
To create the pager in our views I created the following razor helper (twitter bootstrap compatible):
@using System;
@helper If(bool condition, string then) {
if(condition) { <text>@then</text> }
}
@helper Pager(int currentPage, int totalPages, Func<int, string> pageUrl, string cssClass = null) {
if (totalPages > 1) {
<div class="pagination @cssClass">
<ul>
@for (int i = 1; i < totalPages + 1; i++) {
<li @If(i == (currentPage + 1), then: "class=active")>
<a href="@pageUrl(i)">@i</a>
</li>
}
</ul>
</div>
}
}
The helper takes in a Func<int, string>
for generating the page link urls. Assuming our view model is a IPagedList we add a pager like so:
@RazorHelpers.Pager(
Model.PageIndex,
Model.TotalPages,
x => Url.Action("index", new { page = x })
)
Paging has never been so simple.