Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Handle GridView.OnSorting() and create sorting expression dynamically using LINQ

4.90/5 (16 votes)
18 Jul 2014CPOL1 min read 105.7K   963  
How to create a sorting expression from GridViewSortEventArgs using LINQ Expression Tree.

Introduction

The GridView control from ASP.NET 2.0 such as this one is great and widely used:

ASP.NET
<asp:GridView runat="server" ID="gridPersons" 
         AutoGenerateColumns="false" AllowSorting="true" 
         OnSorting="gridPersons_Sorting">
    <Columns>
        <asp:BoundField HeaderText="First name" 
           DataField="FirstName" SortExpression="FirstName" />
        <asp:BoundField HeaderText="Last name" 
           DataField="LastName" SortExpression="LastName" />
    </Columns>
</asp:GridView>
C#
protected void gridPersons_Sorting(object sender, GridViewSortEventArgs e)
{
    string sortExpression = e.SortExpression;
}

But has one significant limitation: sorting event argument GridViewSortEventArg is based on string values from markup. So it can't be directly used in programmatic sorting using LINQ,

This article describes how to convert a string argument into the sorting expression.

Background

Let's assume we have a sequence of strongly typed class, say Person, and we want to bind it to a GridView and then sort it by clicking on the appropriate column:

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

IEnumerable<Person> persons = GetPersons();
gridPersons.DataSource =  persons.ToArray();
gridPersons.DataBind();

Generally speaking, we could hard-code both the sorting expression (i.e., property to sort by) and the direction (ascending, descending):

C#
protected void gridPersons_Sorting(object sender, GridViewSortEventArgs e)
{
    Func<Person, object> prop = null;
    switch (e.SortExpression)
    {
        case "FirstName":
        {
            prop = p => p.FirstName;
            break;
        }
        case "LastName":
        {
            prop = p => p.LastName;
            break;
        }
    }

    Func<IEnumerable<Person>, Func<Person, object>, IEnumerable<Person>> func = null;

    switch (e.SortDirection)
    {
        case SortDirection.Ascending:
        {
            func = (c, p) => c.OrderBy(p);
            break;
        }
        case SortDirection.Descending:
        {
            func = (c, p) => c.OrderByDescending(p);
            break;
        }
    }

    IEnumerable<Person> persons = GetPersons();
    persons = func(persons, prop);

    gridPersons.DataSource = persons.ToArray();
    gridPersons.DataBind();
}

But such approach contains a number of disadvantages: all the object's properties are hard-coded, as well as the sorting directions. Much better would be to create all these dynamically.

Solution

Let's encapsulate the logic into a number of dedicated classes.

The first one converts SortDirection to Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> which is a function accepting a sequence and another function accepting object and returning one of its properties; and returning that sequence sorted by that object's property.

C#
public static class SortExpressionConverter<T>
{
    private static IDictionary<SortDirection, ISortExpression> expressions = 
        new Dictionary<SortDirection, ISortExpression>
    {
        { SortDirection.Ascending, new OrderByAscendingSortExpression() },
        { SortDirection.Descending, new OrderByDescendingSortExpression() }
    };

    interface ISortExpression
    {
        Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> GetExpression();
    }

    class OrderByAscendingSortExpression : ISortExpression
    {
        public Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> GetExpression()
        {
            return (c, f) => c.OrderBy(f);
        }
    }

    class OrderByDescendingSortExpression : ISortExpression
    {
        public Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> GetExpression()
        {
            return (c, f) => c.OrderByDescending(f);
        }
    }

    public static Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>>
        Convert(SortDirection direction)
    {
        ISortExpression sortExpression = expressions[direction];
        return sortExpression.GetExpression();
    }
}

The second one builds a lambda expression Expression<Func<T, object>> and returns underlying lambda Func<T, object>:

C#
public static class SortLambdaBuilder<T>
{
    public static Func<T, object> Build(string columnName, SortDirection direction)
    {
        // x
        ParameterExpression param = Expression.Parameter(typeof(T), "x");

        // x.ColumnName1.ColumnName2
        Expression property = columnName.Split('.')
                                        .Aggregate<string, Expression>
                                        (param, (c, m) => Expression.Property(c, m));

        // x => x.ColumnName1.ColumnName2
        Expression<Func<T, object>> lambda = Expression.Lambda<Func<T, object>>(
            Expression.Convert(property, typeof(object)),
            param);
        
        Func<T, object> func = lambda.Compile();
        return func;
    }
}

Wrap how these classes are used into an extension method:

C#
public static class CollectionExtensions
{
    public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> collection, 
           string columnName, SortDirection direction)
    {
        Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> expression =
            SortExpressionConverter<T>.Convert(direction);

        Func<T, object> lambda =
            SortLambdaBuilder<T>.Build(columnName, direction);

        IEnumerable<T> sorted = expression(collection, lambda);
        return sorted;
    }
}

So now we have a flexible, fully dynamic and generic solution:

C#
protected void gridPersons_Sorting(object sender, GridViewSortEventArgs e)
{
    IEnumerable<Person> persons = GetPersons();
    persons = persons.OrderBy(e.SortExpression, e.SortDirection);

    gridPersons.DataSource = persons.ToArray();
    gridPersons.DataBind();
}

Points of Interest 

Here are some discussions on StackOverflow related to the subject:

History of Changes

  • 27/08/2011 - Initial publication
  • 22/03/2012 - OrderBy<T>() extension method expanded to make it easier to read and understand
  • 17/07/2014 - SortLambdaBuilder<T> supports nested properties

License

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