For .NET 3.5 and later we can use the
System.Linq.Dynamic
library available for download from NuGet
If we have a collection of Car objects with properties Make, Model, Year and Price...
class Car
{
public string Make { get; set; }
public string Model { get; set; }
public int Year { get; set; }
public decimal Price { get; set; }
}
...this will sort the collection on multiple properties using lambda expression.
cars.OrderBy(c => c.Make)
.ThenBy(c => c.Model)
.ThenByDescending(c => c.Year)
.ThenByDescending(c => c.Price);
I prefer doing this instead.
// prefix property name with a dash for reverse sort
cars.AsQueryable()
.OrderBy("Make,Model,-Year,-Price");
// sort on one property
cars.AsQueryable()
.OrderBy("Year");
// array of properties
cars.AsQueryable()
.OrderBy(new string[] {"Year", "Price"});
It is made possible by dynamic lambda expressions. Copy the code below to a static class in your project. I have mine at Helpers/Extension.cs
.
public static class Extensions
{
public static IOrderedQueryable<T> OrderBy<T>(
this IQueryable<T> source,
string property)
{
if (property.IndexOf(',') != -1)
{
return OrderBy<T>(
source,
property.Split(','));
}
else if (property.StartsWith("-"))
{
return ApplyOrder<T>(
source,
property.Substring(1).Trim(),
"OrderByDescending");
}
else
{
return ApplyOrder<T>(
source,
property,
"OrderBy");
}
}
public static IOrderedQueryable<T> OrderByDescending<T>(
this IQueryable<T> source,
string property)
{
return ApplyOrder<T>(
source,
property,
"OrderByDescending");
}
public static IOrderedQueryable<T> ThenBy<T>(
this IOrderedQueryable<T> source,
string property)
{
return ApplyOrder<T>(
source,
property,
"ThenBy");
}
public static IOrderedQueryable<T> ThenByDescending<T>(
this IOrderedQueryable<T> source,
string property)
{
return ApplyOrder<T>(
source,
property,
"ThenByDescending");
}
static IOrderedQueryable<T> ApplyOrder<T>(
IQueryable<T> source,
string property,
string methodName)
{
string[] props = property.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach (string prop in props)
{
// use reflection (not ComponentModel) to mirror LINQ
PropertyInfo pi = type.GetProperty(
prop,
(BindingFlags.NonPublic
| BindingFlags.Public
| BindingFlags.Instance);
// raise error if property is not found
if (pi == null)
{
throw new ArgumentException(string.Format(
"Sort Error. Property '{0}' not found on type {1}",
prop, type.FullName));
}
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
object result = typeof(Queryable)
.GetMethods()
.Single(method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { source, lambda });
return (IOrderedQueryable<T>)result;
}
public static IOrderedQueryable<T> OrderBy<T>(
this IQueryable<T> source,
IEnumerable<string> properties)
{
// iterate the properties and sort
IOrderedQueryable<T> sortedData = null;
foreach (string prop in properties)
{
string propName = prop.Trim();
bool ascending = true;
if (string.IsNullOrEmpty(propName))
{
continue;
}
if (propName.StartsWith("-"))
{
ascending = false;
propName = prop.Substring(1).Trim();
}
// first iteration
if (sortedData == null)
{
if (ascending)
{
sortedData = source.AsQueryable().OrderBy(propName);
}
else
{
sortedData = source.AsQueryable().OrderByDescending(propName);
}
}
else
{
// subsequent iterations...
if (ascending)
{
sortedData = sortedData.ThenBy(propName);
}
else
{
sortedData = sortedData.ThenByDescending(propName);
}
}
}
return sortedData;
}
}
Related:
http://stackoverflow.com/questions/41244/dynamic-linq-orderby-on-ienumerablet/233505#233505