Introduction
It's been a while since I last wrote an article. I like coding, but I'm not a very good writer. However, sometimes, I do feel the need to share my work to others. Maybe, just maybe, there's someone out there scratching his/her head off trying to solve the same problem I already solved. And recently, I had a eureka moment. I was so excited I tried sharing it to my girlfriend, but I got a blank stare in return. So, I'm sharing it to you!
Most of us had to deal with pagination. I noticed I've tackled this issue so many times before, but I never had a real solution. I never had a solution that made myself go "wow!" I always used a quick and dirty implementation just so I can get past it. Last week, that changed and today, I'm giving it to you!
Background
You see, pagination is not as simple as it looks. True, you can throw in a few lines of code and voila! You've got yourself a working application. I've seen a lot of implementation myself, but nothing really satisfied me. Here's one example:
public IEnumerable<ComplexType> GetComplexTypes(int pageSize, int pageNumber)
{
return someContext.Get<ComplexType>()
.Skip(pageSize * (pageNumber - 1))
.Take(pageSize);
}
Okay, that looks good, you might say. It does give you the basic functionality of pagination. However, suppose I have a Grid View on my web page that should display the maximum page number in the footer section. How would I know what number to display?
Eureka!
Using the code
Before we dive right in to the details, let me show you how easy it is to use the code:
public class Repository<T> : IRepository<T>
{
private readonly IContext someContext;
public Repository(IContext someContext)
{
this.someContext = someContext;
}
public IEnumerable<T> GetComplexTypes(Paging paging)
{
return this.someContext.Get<T>()
.Paginate<T>(paging); }
}
public class SomeService
{
private readonly IRepository<ComplexType> repository;
public SomeService(IRepository<ComplexType> repository)
{
this.repository = repository;
}
public List<ComplexType> GetComplexTypes(int pageSize, int pageNumber, out int pageCount)
{
var paging = new Paging(pageSize, pageNumber);
var complexTypes = this.repository
.GetComplexTypes(paging)
.ToList();
pageCount = paging.PageCount; return complexTypes;
}
}
That's pretty much all you need to know in terms of using the code, but it's probably better to understand how it works.
Implementation
There are only 3 things you need to keep in mind to understand how it all works:
- The
Paging
class - a placeholder for all paging-related parameters such as the page size, page number, and the page count (like an out parameter).
- The
PagedEnumerable
class - implementation of System.Collections.Generic.IEnumerable<T>
. The most important logic is in this class.
- The
EnumerableExtension
class - extends the System.Collections.Generic.IEnumerable<T>
interface with a Paginate()
method.
The Paging class
Apart from having paging-related properties mentioned above, this class also takes care of calculating the value of the PageCount
property when the CalculateAndSetPageCount()
method is executed. You don't need to call this method yourself. This is invoked automatically in the PagedEnumerable.DoGetEnumerator()
method when an Enumerable
extension method such as ToList()
or ToArray()
is called. All you have to do is to pass an instance of this class to the Paginate()
method.
NOTE:
As you may have noticed by now, I prefer using page number instead of page index. If you're more comfortable working with page index, feel free to modify the code to satisfy your needs.
The PagedEnumerable class
The PagedEnumerable
class is an internal
class and it is the most important component of the code. The magic happens in the DoGetEnumerator()
method. This method keeps count of all the items in the collection. However, it returns only those items that satisfy the conditions set against the Paging.PageSize
and Paging.PageNumber
properties.
private IEnumerator<T> DoGetEnumerator()
{
var count = 0;
var size = 0;
var skip = this.paging.GetSkip();
var enumerator = this.collection.GetEnumerator();
while (enumerator.MoveNext())
{
if (++count <= skip)
continue;
if (++size > this.paging.PageSize)
continue;
yield return enumerator.Current;
}
this.paging.CalculateAndSetPageCount(count);
}
As you can see, the Paging.CalculateAndSetPageCount()
method is called right before the the end of the DoGetEnumerator()
method is reached.
The Paginate method
This is the glue that holds all the components together. This is what communicates to the outside world. This is where the magic begins. This method simply returns a PagedEnumerable<T>
object as an IEnumerable<T>
. It's that simple. It's clean and it's elegant!
Conclusion
Thank you for reading my article! I hope you liked it. The best way for you to understand how the code works is to get your hands dirty. So, feel free to download and use it.
A word of caution: I have never used or tested this code in scenarios which involve big data sets. I don't expect it to work efficiently in such cases. So, use it with caution.
That' all folks. Happy coding!
History
- 17 July 2014: Initial version
- 17 July 2014: Fixed image link