Self Sorting GridView with LINQ Expression Trees
(
Apr 18 2008 - 11:21:04 PM by
Timothy Khouri) - [
print article]
The GridView control in ASP.NET 2.0 is lacking some major (yet common) functionality - sorting. This article will show you how to create a "self-sorting" GridView using LINQ expression trees.
First, it's important to explain what I mean when I say that the GridView doesn't have sorting capabilities. You're probably saying to yourself - "I know the GridView has sorting built in because it has a 'Sort' method and 'Sorting' and 'Sorted' events." And that's true, but those are merely placeholders that allow you to write your own sorting code.
Also, if you use the designer to build your GridViews, and bind them to SQL data sources, then you might also be confused about the above statements. But rest assured that the sorting magic is all happening in the terribly inefficient SqlDataSource control, and not the actual GridView itself.
If you're not already familiar with the basics of LINQ, then you might want to first read this article: Learn The Basics of LINQ. To sum up LINQ in one sentence (for the purpose of moving on) I would say: "LINQ is a language feature in .NET that provides a ton of out-of-the-box functionality for querying data."
The Problem - Why The GridView Can't Sort
So why is it that the GridView control doesn't have the ability to sort by default? The problem lies in the fact that the data source is very dynamic, could be of any type and could even be just a paged subset of the ultimate source.
Another problem is performance. If your data source was from a SQL table, then you would want to pass the sorting functionality on to the SQL Server itself to gain the most performance. Because the GridView doesn't know what your source will be, it can't really provide the needed functionality.
The Solution - LINQ
Since LINQ provides querying capabilities on a generic level, the above problems are no problem. Because LINQ to objects can handle *any* enumerable objects, and LINQ to SQL is smart enough to pass the sorting logic on to SQL Server, you win no matter what.
For example, imagine if the data source was a collection of MembershipUsers (via the Membership.GetUsers method). Here's how easy it would be to sort using LINQ and the OrderBy method:
myGridView.DataSource = Membership.GetUsers()
.OfType<MembershipUser>()
.OrderBy(user => user.Name);
That was very easy to do, but that's because we know the data type of our source at compile time. But if we are going to make a generic "self sorting GridView", then we have to build our functionality with the limitation of not knowing the data type of the data source or the name of the field that is being sorted. So we'll need to reconstruct this functionality at runtime by building "expression trees" (the power behind LINQ).
Step One - Build Our GridView Control
To begin with, let's build a basic control that inherits from GridView , and stub out a placeholder to put our sorting logic.
public class SelfSortingGridView : GridView
{
protected override void OnSorting(GridViewSortEventArgs e)
{
}
}
Now that we have our custom GridView control ready to go, we can move on to step two.
Step Two - Creating an Expression to Sort With
As was stated earlier, we need to build an expression that will tell LINQ how we want to sort our data source. There are a few tricky issues here that we need to identify and overcome. If we try to copy the example found in this blog post to reproduce our sorting example at the top of this article, we would get half-way there:
var param = Expression.Parameter(typeof(MembershipUser), "user");
var sortExpression = Expression.Lambda<Func<MembershipUser, object>
(Expression.Property(param, "Name"), param);
var sortExpression = Expression.Lambda<Func<MembershipUser, object>
(Expression.Convert(Expression.Property(param, "Name"), typeof(object)), param);
myGridView.DataSource = Membership.GetUsers()
.OfType<MembershipUser>().OrderBy(sortExpression);
So we've solved one issue - that of finding the property to sort with by it's name. The "Expression.Property" method above did that for us. Now, we have to solve the second issue - not knowing what the data type of the data source will be until runtime. To solve this in the fewest lines of code, I'm going to make a custom generic class that will create the sort expression for us so that we can simply supply the type at runtime.
public class GenericSorter<T>
{
public IEnumerable<T> Sort(IEnumerable<T> source, string sortBy)
{
var param = Expression.Parameter(typeof(T), "item");
var sortExpression = Expression.Lambda<Func<T, object>>
(Expression.Convert(Expression.Property(param, sortBy), typeof(object)), param);
return source.AsQueryable<T>().OrderBy<T, object>(sortExpression);
}
}
Beautiful! Notice that because we are using a generic class, we can tell the compiler that we want to use the type "T" (whatever "T" will be at runtime, we don't care)! So, if I wanted to create an instance of my GenericSorter class at runtime, passing in a type that I won't know until runtime, and then call the "Sort" method, I would just do some simple reflection as will be shown below.
For simplicities sake, the data source is stored in a variable called "_data". You can download the whole source code at the end of the article to see exactly how it's done. Here's a sample of the code:
Type dataSourceType = _data.GetType();
Type dataItemType = typeof(object);
if (dataSourceType.HasElementType)
{
dataItemType = dataSourceType.GetElementType();
}
else if (dataSourceType.IsGenericType)
{
dataItemType = dataSourceType.GetGenericArguments()[0];
}
Type sorterType = typeof(GenericSorter<>).MakeGenericType(dataItemType);
var sorterObject = Activator.CreateInstance(sorterType);
this.DataSource = sorterType.GetMethod("Sort", new Type[] { dataSourceType, typeof(string) })
.Invoke(sorterObject, new object[] { _data, e.SortExpression });
this.DataBind();
Conclusion
At first glance, it may seem like we're doing a lot of code just for sorting functionality. You may even be thinking that this is an extremely convoluted way of sorting a GridView, but really it's not. Once you understand what LINQ is doing, and how to build your own expression trees, you'll really appreciate this new language feature in .NET.
I'll include a web project that has the full source code and some examples of different objects that can be sorted here. If you first don't understand what all is going on, I'd recommend that you download the source code and step through it in the debugger.
I hope this article has excited you about LINQ and expression trees. If you're not excited, I hope your informed. If you don't feel informed, I hope at least that you're not now afraid of LINQ.
Here's the source code and sample project: SingingEels_LinqSelfSortingGridView.zip
Jun 04 2008I've updated the source code to include the fix that was made due to the comments below.