How To Properly Return A Paged Result Set From Your Repository

If you follow this blog then you know that I discourage the practice of returning IQueryable<T> from your repositories. A common criticism I receive about my use of "verbose repositories" (repositories that have very specific methods) is that returning paged result sets and calculating the maximum number of pages requires two methods on the repository and it's silly to have an entire method dedicated to returning a count.

public interface IBookRepository  
{
    IList<Book> GetBooksByPage(int page, int itemsPerPage);
    int GetBookCount();
}

public class BookRepository : IBookRepository  
{
    private Entities _objectContext;

    public BookRepository()
    {
        _objectContext = new Entities();
    }

    public IList<Book> GetBooksByPage(int page, int itemsPerPage)
    {
        return _objectContext.Books.Skip(itemsPerPage * (page-1)).Take(itemsPerPage).ToList();
    }

    public int GetBookCount()
    {
        return _objectContext.Books.Count();
    }
}

public class LibraryController : Controller  
{
    int _itemsPerPage = 15;

    public ActionResult ShowBooks(int page)
    {
        IBookRepository bookRepo = new BookRepository(); // Normally this would be injected.
        ViewBag.TotalPages = bookRepo.Books.Skip(itemsPerPage * (page-1)).Take(itemsPerPage).ToList();
        ViewBag.Books = bookRepo.GetBooksByPage(page, _itemsPerPage);
        return View();
    }
}

This example is often used as a reason to return IQueryable from your repository because you can simplify the above code to look like this:

public interface IBookRepository  
{
    IQueryable<Book> Books { get; }
}

public class BookRepository : IBookRepository  
{
    private Entities _objectContext;

    public BookRepository()
    {
        _objectContext = new Entities();
    }

    public IQueryable<Book> Books { get { return _objectContext.Books; } }
}

public class LibraryController : Controller  
{
    int _itemsPerPage = 15;

    public ActionResult ShowBooks(int page)
    {
        IBookRepository bookRepo = new BookRepository(); // Normally this would be injected.
        ViewBag.TotalPages = (int)(Math.Ceiling((Decimal)bookRepo.Books.Count() / (Decimal)_itemsPerPage));
        ViewBag.Books = bookRepo.Books.Skip(itemsPerPage * (page-1)).Take(itemsPerPage).ToList();
        return View();
    }
}

First of all, I don't really think this is simplifying anything. All it really does is shift some logic from the repository to the controller and removes one method from the repository. Secondly, if you really want to simplify like this then why not take the extra step and just dump the ObjectContext right into the controller and eliminate your repositories altogether? Lastly, it's not true that you have to have an entire method dedicated to returning a count. With a little ground work we can build a reusable infrastructure that makes returning paged result sets incredibly simple and still allows us to use the "verbose repository" pattern. Try this on for size.

/// <summary>
/// Holds relevant information related to a page of a collection of information.
/// </summary>
public class CollectionPage<T>  
{
    /// <summary>
    /// A page of items.
    /// </summary>
    public IList<T> Items { get; set; }

    /// <summary>
    /// Total number of items, regardless of page.
    /// </summary>
    public int TotalItems { get; set; }

    /// <summary>
    /// The number of items that should be shown per page.
    /// </summary>
    public int ItemsPerPage { get; set; }

    /// <summary>
    /// The total number of pages.
    /// </summary>
    public int TotalPages()
    {
        return (int)(Math.Ceiling((Decimal)this.TotalItems / (Decimal)this.ItemsPerPage));
    }
}

public interface IBookRepository  
{
    CollectionPage<Book> GetBooksByPage(int page, int itemsPerPage);
}

public class BookRepository : IBookRepository  
{
    private Entities _objectContext;

    public BookRepository()
    {
        _objectContext = new Entities();
    }

    public CollectionPage<Book> GetBooksByPage(int page, int itemsPerPage)
    {
        var pageOfBooks = new CollectionPage<Book>
        {
            TotalItems = _objectContext.Books.Count(),
            ItemsPerPage = itemsPerPage,
            Items = _objectContext.Books.Skip(itemsPerPage * (page-1)).Take(itemsPerPage).ToList(),
        };
        return pageOfBooks;
    }
}

public class LibraryController : Controller  
{
    int _itemsPerPage = 15;

    public ActionResult ShowBooks(int page)
    {
        IBookRepository bookRepo = new BookRepository(); // Normally this would be injected.
        var pageOfBooks = bookRepo.GetBooksByPage(page, _itemsPerPage);
        ViewBag.TotalPages = pageOfBooks.TotalPages();
        ViewBag.Books = pageOfBooks.Items;
        return View();
    }
}

See how much cleaner that is? It maintains proper separation of concerns and encapsulates all the data you need in one neat little object. I think people often forget just how useful a simple generic object can be. I think this code looks so much cleaner and it's so easy to maintain because you can easily see what is going on. Feel free to steal my CollectionPage<T> object if you'd like.

/// <summary>
/// Holds relevant information related to a page of a collection of information.
/// </summary>
public class CollectionPage<T>  
{
    /// <summary>
    /// A page of items.
    /// </summary>
    public IList<T> Items { get; set; }

    /// <summary>
    /// Total number of items, regardless of page.
    /// </summary>
    public int TotalItems { get; set; }

    /// <summary>
    /// The number of items that should be shown per page.
    /// </summary>
    public int ItemsPerPage { get; set; }

    /// <summary>
    /// The total number of pages.
    /// </summary>
    public int TotalPages()
    {
        return (int)(Math.Ceiling((Decimal)this.TotalItems / (Decimal)this.ItemsPerPage));
    }
}

Chev

Read more posts by this author.

comments powered by Disqus