Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Pagination Solution using IEnumerable<T>

0.00/5 (No votes)
17 Jul 2014 1  
An elegant approach to pagination

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)
{
    // TODO: Validate parameters here ...
    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?

pagination

Eureka!

Using the code

Before we dive right in to the details, let me show you how easy it is to use the code:

// Let's say you have a repository class like this
public class Repository<T> : IRepository<T>
{
    private readonly IContext someContext;

    public Repository(IContext someContext)
    {
        this.someContext = someContext;
    }

    // Pass in a Paging object as a parameter
    public IEnumerable<T> GetComplexTypes(Paging paging)
    {
        // TODO: Validate parameters here ...
        return this.someContext.Get<T>()
            .Paginate<T>(paging); // Then call the .Paginate<T>() method
    }
}

// This is just an example to show how to get the page count
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; // Page count is available after calling the .ToList() method
        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:

  1. 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).
  2. The PagedEnumerable class - implementation of System.Collections.Generic.IEnumerable<T>. The most important logic is in this class.
  3. 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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here