Dynamic SQL-like Linq OrderBy Extension

Dynamic SQL-like Linq OrderBy Extension

Anyway, hope someone finds it useful. And if you see any areas that could use some TLC please let me know A
Posted by Adam Anderson at 7:48 PM  

http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html

So, it’s been a while, but I thought I take moment and do my annual blog post ;).

I’ve been playing around with ASP.NET MVC and the Linq stuff for NHibernate recently. I was in need of an OrderBy extension method that could take a SQL-Like OrderBy string and sort a IQueryable<> or IEnumerable<> collection. I wrote up an implementation that worked, but I just wasn’t satisfied with its internals (quite a bit of reflection to get the correct type to construct a LambdaExpression, etc)

At any rate, I couldn’t leave well enough alone, and, after a bit of Googling, I ran across this StackOverflow answer aboutDynamic LINQ OrderBy. The extension method wasn’t exactly what I was looking for, but that ApplyOrder method is slick, and solved the portion of my implementation that was bothering me.

So, I though I would post up my version in case anybody finds it useful. It handles the following inputs:

list.OrderBy("SomeProperty");
list.OrderBy("SomeProperty DESC");
list.OrderBy("SomeProperty DESC, SomeOtherProperty");
list.OrderBy("SomeSubObject.SomeProperty ASC, SomeOtherProperty DESC");

Dynamic SQL-like Linq OrderBy Extension

    public static class OrderByHelper
{
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> enumerable, string orderBy)
{
return enumerable.AsQueryable().OrderBy(orderBy).AsEnumerable();
}public static IQueryable<T> OrderBy<T>(this IQueryable<T> collection, string orderBy)
{
foreach(OrderByInfo orderByInfo in ParseOrderBy(orderBy))
collection = ApplyOrderBy<T>(collection, orderByInfo);return collection;
}private static IQueryable<T> ApplyOrderBy<T>(IQueryable<T> collection, OrderByInfo orderByInfo)
{
string[] props = orderByInfo.PropertyName.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);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
string methodName = String.Empty;

if (!orderByInfo.Initial && collection is IOrderedQueryable<T>)
{
if (orderByInfo.Direction == SortDirection.Ascending)
methodName = «ThenBy»;
else
methodName = «ThenByDescending»;
}
else
{
if (orderByInfo.Direction == SortDirection.Ascending)
methodName = «OrderBy»;
else
methodName = «OrderByDescending»;
}

//TODO: apply caching to the generic methodsinfos?
return (IOrderedQueryable<T>)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[] { collection, lambda });

}

private static IEnumerable<OrderByInfo> ParseOrderBy(string orderBy)
{
if (String.IsNullOrEmpty(orderBy))
yield break;

string[] items = orderBy.Split(‘,’);
bool initial = true;
foreach(string item in items)
{
string[] pair = item.Trim().Split(‘ ‘);

if (pair.Length > 2)
throw new ArgumentException(String.Format(«Invalid OrderBy string ‘{0}’. Order By Format: Property, Property2 ASC, Property2 DESC»,item));

string prop = pair[0].Trim();

if(String.IsNullOrEmpty(prop))
throw new ArgumentException(«Invalid Property. Order By Format: Property, Property2 ASC, Property2 DESC»);

SortDirection dir = SortDirection.Ascending;

if (pair.Length == 2)
dir = («desc».Equals(pair[1].Trim(), StringComparison.OrdinalIgnoreCase) ? SortDirection.Descending : SortDirection.Ascending);

yield return new OrderByInfo() { PropertyName = prop, Direction = dir, Initial = initial };

initial = false;
}

}

private class OrderByInfo
{
public string PropertyName { get; set; }
public SortDirection Direction { get; set; }
public bool Initial { get; set; }
}

private enum SortDirection
{
Ascending = 0,
Descending = 1
}
}

Deja un comentario