Click here to Skip to main content
Click here to Skip to main content

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

By , 10 Sep 2012
 

Introduction

The GridView control from ASP.NET 2.0 is great and widely used but has a significant limitation: the sorting event argument GridViewSortEventArg is string-based and is based on values in markup. So it can't be directly used in programmatic sorting using LINQ.

<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>
protected void gridPersons_Sorting(object sender, GridViewSortEventArgs e)
{
    string sortExpression = e.SortExpression;
}

This article describes one of the methods to convert a string argument into a 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:

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):

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 an 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 that dynamically.

Solution

First let's wrap the sorting directions into classes and a dedicated helper class:

public static class SortExpressionBuilder<T>
{
    private static IDictionary<SortDirection, ISortExpression> directions = 
            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>> CreateExpression(SortDirection direction)
    {
        return directions[direction].GetExpression();
    }
}

And wrap property selector created dynamically using LINQ Expression Tree into a handy extension method:

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> lambda = Expression.Lambda<Func<T, object>>(        // x => x.ColumnName
            Expression.Convert(property, typeof(object)),
            param)
        .Compile();

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

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

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

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

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

Points of Interest 

Here are some discussions on StackOverflow related to the subject:

History 

  • 27/08/2011 - Version 1.0 - The initial release.
  • 22/03/2012 - Version 1.0.1 - OrderBy<T>() extension method expanded to make it more easy to understand

License

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

About the Author

Alexander M. Batishchev
Software Developer Microsoft
United States United States
Member
.NET developer and architect

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberJuhi Paunikar18 Apr '13 - 3:13 
I'n new with Linq ...It was very helpful for me...thanks you thank you so much....
GeneralRe: My vote of 5memberAlexander M. Batishchev18 Apr '13 - 19:07 
Thank you! Very glad when something I wrote really helps.
SuggestionEnabling sub-property sortingmemberiJazz Pizz31 Jan '13 - 5:37 
I've improved the extension method to enable sub-property sorting.
 
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> collection, string columnName, SortDirection direction)
	{
		ParameterExpression param = Expression.Parameter(typeof(T), "x");   // x

		Expression property;
		if (!columnName.Contains("."))
			property = Expression.Property(param, columnName);       // x.ColumnName

		else //has sub-properties
		{
			string[] subProperties = columnName.Split('.');
			property = subProperties.Aggregate<string, Expression>(param, (result, subPropertyName) => result = Expression.Property(result, subPropertyName)); // x.First.Second.Third and so on
		}
 
		Func<T, object> lambda = Expression.Lambda<Func<T, object>>(   // x => x.ColumnName
						Expression.Convert(property, typeof(object)),
						param)
				.Compile();
 
		Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> expression = SortExpressionBuilder<T>.CreateExpression(direction);
 
		IEnumerable<T> sorted = expression(collection, lambda);
		return sorted;
	}

GeneralRe: Enabling sub-property sortingmemberAlexander M. Batishchev18 Apr '13 - 19:17 
Sorry missed a notification email regarding your comment. It's great, thank you for addition!
A question here: where is such notation as Prop1.Prop2.Prop3 used? Does GridView support it? Just out of courisity.
QuestionDynamic.csmembertmbgfan22 Mar '12 - 6:32 
Just an FYI. Microsoft created an "extension" class that allows LINQ-to-SQL sorting using a string. ScottGu blogged about it here. The download page (C#) is here.
 
Essentially it allows you to write LINQ-to-SQL commands like
.OrderBy("ColumnName desc")
.
 
The class file can be found in "LinqSamples/DynamicQuery/DynamicQuery/Dynamic.cs" when you download the examples file.
AnswerRe: Dynamic.csmemberAlexander M. Batishchev22 Mar '12 - 7:11 
Sweet. Will try. Have you tried already?
GeneralRe: Dynamic.csmembertmbgfan22 Mar '12 - 8:16 
Not recently. But yes, I have used it before. Your solution is probably more lightweight since it focuses specifically on converting a string into a sorting expression, but I thought it might be of interest to you or others reading your article.
QuestionExplainationmemberKnvnBhat21 Mar '12 - 19:52 
Nice post. Could you please explain this? specially on the section
return SortExpressionBuilder<T>.CreateExpression(direction)(collection, func);
on OrderBy extension method of the IEnumerable.
 
As per my understanding CreateExpression is a static method which takes SortDirection as argument. I wonder how (collection, func) will be taken by that method or SortExpressionBuilder class?
AnswerRe: ExplainationmemberAlexander M. Batishchev21 Mar '12 - 21:53 
With pleasure.
 
We can split the one-line return expression into several, more detailed lines:
// result of expression tree construction and compilation
Func<T, object> func = Expression.Lambda<Func<T, object>>(...).Compile(); // e.g. p => p.FirstName

// accepts:
//    IEnumerable<T> (sequence to sort)
//    Func<T, object> (sorting expression to sort by)
// returns:
//    IEnumerable<T> (resulting sorted sequence) 
Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> expression =
     SortExpressionBuilder<T>.CreateExpression(direction);
 
IEnumerable<T> sorted = expression(collection, func); // i.e. collection.OrderBy(p => p.FirstName)
return sorted;
 
Also I've updated the article itself to make it be more clear.
QuestionIt does not work properly...memberjulgon200220 Mar '12 - 8:23 
It does not work properly if the property of the class to be sorted, for example, is a numeric value. Thumbs Down | :thumbsdown:

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 10 Sep 2012
Article Copyright 2011 by Alexander M. Batishchev
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid