Click here to Skip to main content
15,884,629 members
Articles / Web Development / ASP.NET

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

Rate me:
Please Sign up or sign in to vote.
4.90/5 (16 votes)
18 Jul 2014CPOL1 min read 102.1K   963   24   25
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)


Written By
Software Developer
United States United States
.NET developer and architect

Comments and Discussions

 
QuestionAscending and Descending on IEnumerable in LINQ Pin
matrix38827-Apr-15 8:43
matrix38827-Apr-15 8:43 
QuestionNot supporting custom properties -- complex objects Pin
Member 1079109110-Jun-14 6:53
Member 1079109110-Jun-14 6:53 
This code works fine for objects with regular properties but not with custom properties(nested properties)

XML
Able to sort ORDERID and OrderNumber BUT NOT CustomerInfo Property

<pre lang="cs">public class Order
{
    public Int32 OrderID{ get; set; }
    public String OrderNumber { get; set; }
    public Customer CustomerInfo { get; set;}
}

public class Customer
{
    public Int64 CustomerNumber { get; set; }
    public String FirstName { get; set; }
    public String  LastName { get; set; }
    public String Phone { get; set; }
}</pre>


XML
My Front End grid has columns defined one of the columns is

<asp:TemplateField HeaderText="Last Name"  ItemStyle-Width="15%" SortExpression="OrderCustomerInfo.LastName">
    <ItemTemplate>
        <%# Eval("OrderCustomerInfo.LastName") %>
    </ItemTemplate>
</asp:TemplateField>


XML
When I am passing Sort Expression : OrderCustomerInfo.LastName it's not recognizing the column below is my sort function, I am getting error --

Instance property 'OrderCustomerInfo.LastName' is not defined for type 'Order' Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.ArgumentException: Instance property 'OrderCustomerInfo.LastName' is not defined for type 'Order' )

Error Line -- Expression property = Expression.Property(param, columnName); // x.ColumnName

public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> collection, string columnName, SortDirection direction)
{
    ParameterExpression param = Expression.Parameter(typeof(T), "x"); // x
    Expression property = Expression.Property(param, columnName);     // x.ColumnName
    Func<T, object> func = Expression.Lambda<Func<T, object>>(        // x => x.ColumnName
    Expression.Convert(Expression.Property(param, columnName),
    typeof(object)), param).Compile();

    Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> expression =
        SortExpressionBuilder<T>.CreateExpression(direction);
    IEnumerable<T> sorted = expression(collection, func);

    return sorted;
}

AnswerRe: Not supporting custom properties -- complex objects Pin
Alexander Batishchev12-Jun-14 12:10
Alexander Batishchev12-Jun-14 12:10 
GeneralRe: Not supporting custom properties -- complex objects Pin
Member 1079109116-Jul-14 8:08
Member 1079109116-Jul-14 8:08 
GeneralRe: Not supporting custom properties -- complex objects Pin
Alexander Batishchev17-Jul-14 8:25
Alexander Batishchev17-Jul-14 8:25 
GeneralRe: Not supporting custom properties -- complex objects Pin
Member 1079109117-Jul-14 8:33
Member 1079109117-Jul-14 8:33 
AnswerRe: Not supporting custom properties -- complex objects Pin
Alexander Batishchev17-Jul-14 11:31
Alexander Batishchev17-Jul-14 11:31 
GeneralRe: Not supporting custom properties -- complex objects Pin
Alexander Batishchev21-Jul-14 10:07
Alexander Batishchev21-Jul-14 10:07 
GeneralRe: Not supporting custom properties -- complex objects Pin
Member 1079109121-Jul-14 11:05
Member 1079109121-Jul-14 11:05 
GeneralMy vote of 5 Pin
crazie.coder18-Apr-13 3:13
professionalcrazie.coder18-Apr-13 3:13 
GeneralRe: My vote of 5 Pin
Alexander Batishchev18-Apr-13 19:07
Alexander Batishchev18-Apr-13 19:07 
SuggestionEnabling sub-property sorting Pin
iJazz Pizz31-Jan-13 5:37
iJazz Pizz31-Jan-13 5:37 
GeneralRe: Enabling sub-property sorting Pin
Alexander Batishchev18-Apr-13 19:17
Alexander Batishchev18-Apr-13 19:17 
GeneralRe: Enabling sub-property sorting Pin
sashidhar18-Sep-13 21:43
sashidhar18-Sep-13 21:43 
QuestionDynamic.cs Pin
tmbgfan22-Mar-12 6:32
tmbgfan22-Mar-12 6:32 
AnswerRe: Dynamic.cs Pin
Alexander Batishchev22-Mar-12 7:11
Alexander Batishchev22-Mar-12 7:11 
GeneralRe: Dynamic.cs Pin
tmbgfan22-Mar-12 8:16
tmbgfan22-Mar-12 8:16 
QuestionExplaination Pin
K. Naveen. Bhat21-Mar-12 19:52
K. Naveen. Bhat21-Mar-12 19:52 
AnswerRe: Explaination Pin
Alexander Batishchev21-Mar-12 21:53
Alexander Batishchev21-Mar-12 21:53 
QuestionIt does not work properly... Pin
julgon201220-Mar-12 8:23
julgon201220-Mar-12 8:23 
QuestionRe: It does not work properly... Pin
Alexander Batishchev21-Mar-12 5:22
Alexander Batishchev21-Mar-12 5:22 
SuggestionNeeded changes Pin
Leoric229-Aug-11 2:27
Leoric229-Aug-11 2:27 
GeneralRe: Needed changes Pin
Alexander Batishchev21-Mar-12 21:55
Alexander Batishchev21-Mar-12 21:55 
QuestionGood code but no explanation :( Pin
Dav Zen28-Aug-11 8:02
Dav Zen28-Aug-11 8:02 
AnswerRe: Good code but no explanation :( Pin
Alexander Batishchev28-Aug-11 8:11
Alexander Batishchev28-Aug-11 8:11 

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.