Click here to Skip to main content
15,894,106 members
Articles / Programming Languages / C#
Tip/Trick

Dynamic Sorting in LINQ, Part 3: Pagination

Rate me:
Please Sign up or sign in to vote.
4.50/5 (3 votes)
28 Oct 2013CPOL3 min read 18.8K   5   1
Dynamically sort query results using LINQ expressions and reflection with sorting and paging.

Introduction

Pagination is a typical process in data display whereby large sets of data are broken into discrete sections for viewing, more often than not used in conjunction with some type of grid or list component.  In Part 1 and Part 2 of this series, we walked through the process of dynamic sorting using LINQ to return a sorted collection of objects.  In this tip, we'll walk through the basics of how to add pagination into the mix.

Using the Code

In this tip, we're going to use the basic data model that was defined as a structure in the previous tip:

C#
public struct Person
{
    public string FirstName { get; set; }
    public string MiddleInitial { get; set; }
    public string LastName { get; set; }

    public override string ToString()
    {
        return String.Format("{0}, {1} {2}", LastName, FirstName, MiddleInitial);
    }
}

Next, we'll set up the data store containing a list of these objects:

C#
public class PeopleDataStore
{
    public const bool SortAscending = true;
    public const bool SortDescending = false;

    private List<Person> m_people = null;

    public PeopleDataStore()
    {
        m_people = new List<Person>() {
            new Person() { FirstName = "John", MiddleInitial = "Q", LastName = "Doe" },
            new Person() { FirstName = "Jane", MiddleInitial = null, LastName = "Appleton" },
            new Person() { FirstName = "Jim", MiddleInitial = "H", LastName = "Smith" },
            new Person() { FirstName = "Joe", MiddleInitial = null, LastName = "Plumber" }
        };
    }

    public List<Person> People
    {
        get { return m_people; }
    }

    public List<Person> GetPeople(string sortPropertyName = null, bool sortAscending = SortAscending, int pageIndex = 0, int pageSize = 25)
    {
        int actualIndex = (pageIndex * pageSize);

        List<Person> list = m_people;

        if (!String.IsNullOrEmpty(sortPropertyName)) {
            Type personType = typeof(Person);

            if (personType.GetProperties().Any(prop => prop.Name == sortPropertyName && prop.CanRead)) {
                PropertyInfo pinfo = personType.GetProperty(sortPropertyName);
                ParameterExpression paramExpr = Expression.Parameter(typeof(Person), "instance");
                MemberExpression memberExpr = Expression.Property(paramExpr, pinfo);

                Func<Person, object> orderByFunc = Expression.Lambda<Func<Person, object>>(memberExpr, paramExpr).Compile();

                Func<IEnumerable<Person>, IOrderedEnumerable<Person>> sortFunc = null;
                if (sortAscending) sortFunc = (source => source.OrderBy(orderByFunc));
                else sortFunc = (source => source.OrderByDescending(orderByFunc));

                list = sortFunc(m_people).ToList();
            }
        }

        return (actualIndex <= (list.Count + pageSize) ? list.Skip(actualIndex).Take(pageSize) : list).ToList();
    }
}

Taking a walk through the code, we start out by defining a couple of Boolean constants for better legibility when calling the GetPeople method; either these constants or literal Boolean values can be used for the sortAscending parameter on the method.  Next, we create a strongly-typed list of the Person type to hold our data values, then populate the list in the constructor; providing a corollary read-only property of the same type for access to the list.

The GetPeople method is defined with four optional parameters: the name of the public property from the Person structure used for sorting, a Boolean flag indicating whether the list should be sorted in ascending or descending order, an integer value for the starting index of the paging operation, and an integer value for the size of the page collected from the paging operation.  The paging values are determined by the pageIndex and pageSize parameters: the pageIndex parameter defines where in the collection the paging operation should start while the pageSize parameter defines the number of elements to be retrieved from the collection.  Since the code will not have any notion of where to begin taking elements from the collection, the actualIndex variable is defined based on creating an index which is the product of the number of elements multiplied by the requested page index.

If no values have been supplied for any of the parameters, the method simply returns the list values in their default order.  If the sortPropertyName parameter is supplied, we then check to see if the Person type defines a readable public property of the same name and continue processing should this prove true.  From there, we obtain a PropertyInfo instance from the Person type to be used in the sorting operation.  At this point, the code becomes divergent from the prior tips in that we begin constructing a dynamic lambda expression as the delegate parameter for the OrderBy or OrderByDescending extension methods; the first portion of the expression is a ParameterExpression instance which creates the main parameter for the lambda expression.  Next, we create a MemberExpression instance which identifies the desired property from the parameter value.  The orderByFunc variable is populated by the compiled result of the lambda expression assembly call.

Next, we create another Func delegate to define which sorting method to call based on the sortAscending parameter supplied to the method call.  If no value is supplied for the parameter, collection sorting defaults to sort ascending.  Since we know that the OrderBy and OrderByDescending methods are extensions supplied to the IEnumerable(T) interface and our list implements this interface, we define the Func delegate to access an input parameter of List<Person>.  The output parameter is then defined as the same return type as defined by the OrderBy and OrderByDescending methods, this being an instance of IOrderedEnumerable(T).  With the delegate defined, we then apply sorting to the list.

Finally, we perform a sanity check on the paging values supplied against the number of elements in the source list.  If the list contains fewer elements than is requested by the paging values, the entire list is returned.  If the list contains more element than the paging values, the result of applying the Skip and Take extension methods is returned.

Now to put the code to use in a simple console app:

C#
public static class Program
{
    static void Main()
    {
        Action<Person> consoleAction = (person => Console.WriteLine(person));
        PeopleDataStore dataStore = new PeopleDataStore();

        Console.WriteLine("People listed in default order:");
        dataStore.People.ForEach(consoleAction);

        Console.WriteLine("\nPeople listed in reverse alphabetical order by last name:");
        dataStore.GetPeople("LastName", PeopleDataStore.SortDescending, 0, 2)
            .ForEach(consoleAction);
    }
}

Further Reading

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer Robert Half Technology
United States United States
Polyglot, architect, and general all-around nerd.

Comments and Discussions

 
SuggestionCheck out IX - Interactive Extensions - Buffer<T> Method Pin
Empiric28-Oct-13 6:07
Empiric28-Oct-13 6:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.