Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
Solution 1
Just stumbled into this oldie...
To do this without the dynamic LINQ library, you just need the code as below. This covers most common scenarios including nested properties.
To get it working with IEnumerable<T>
you could add some wrapper methods that go via AsQueryable
- but the code below is the core Expression
logic needed.
public static IOrderedQueryable<T> OrderBy<T>(
this IQueryable<T> source,
string property)
{
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);
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;
}
Edit: it gets more fun if you want to mix that with dynamic
- although note that dynamic
only applies to LINQ-to-Objects (expression-trees for ORMs etc can't really represent dynamic
queries - MemberExpression
doesn't support it). But here's a way to do it with LINQ-to-Objects. Note that the choice of Hashtable
is due to favorable locking semantics:
using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Runtime.CompilerServices;
static class Program
{
private static class AccessorCache
{
private static readonly Hashtable accessors = new Hashtable();
private static readonly Hashtable callSites = new Hashtable();
private static CallSite<Func<CallSite, object, object>> GetCallSiteLocked(
string name)
{
var callSite = (CallSite<Func<CallSite, object, object>>)callSites[name];
if(callSite == null)
{
callSites[name] = callSite = CallSite<Func<CallSite, object, object>>
.Create(Binder.GetMember(
CSharpBinderFlags.None,
name,
typeof(AccessorCache),
new CSharpArgumentInfo[] {
CSharpArgumentInfo.Create(
CSharpArgumentInfoFlags.None,
null)
}));
}
return callSite;
}
internal static Func<dynamic,object> GetAccessor(string name)
{
Func<dynamic, object> accessor = (Func<dynamic, object>)accessors[name];
if (accessor == null)
{
lock (accessors )
{
accessor = (Func<dynamic, object>)accessors[name];
if (accessor == null)
{
if(name.IndexOf('.') >= 0) {
string[] props = name.Split('.');
CallSite<Func<CallSite, object, object>>[] arr
= Array.ConvertAll(props, GetCallSiteLocked);
accessor = target =>
{
object val = (object)target;
for (int i = 0; i < arr.Length; i++)
{
var cs = arr[i];
val = cs.Target(cs, val);
}
return val;
};
} else {
var callSite = GetCallSiteLocked(name);
accessor = target =>
{
return callSite.Target(callSite, (object)target);
};
}
accessors[name] = accessor;
}
}
}
return accessor;
}
}
public static IOrderedEnumerable<dynamic> OrderBy(
this IEnumerable<dynamic> source,
string property)
{
return Enumerable.OrderBy<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> OrderByDescending(
this IEnumerable<dynamic> source,
string property)
{
return Enumerable.OrderByDescending<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> ThenBy(
this IOrderedEnumerable<dynamic> source,
string property)
{
return Enumerable.ThenBy<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> ThenByDescending(
this IOrderedEnumerable<dynamic> source,
string property)
{
return Enumerable.ThenByDescending<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
static void Main()
{
dynamic a = new ExpandoObject(),
b = new ExpandoObject(),
c = new ExpandoObject();
a.X = "abc";
b.X = "ghi";
c.X = "def";
dynamic[] data = new[] {
new { Y = a },
new { Y = b },
new { Y = c }
};
var ordered = data.OrderByDescending("Y.X").ToArray();
foreach (var obj in ordered)
{
Console.WriteLine(obj.Y.X);
}
}
}
Solution 2
Too easy without any complication:
- Add
using System.Linq.Dynamic;
at the top. - Use
vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
Edit: to save some time, the System.Linq.Dynamic.Core (System.Linq.Dynamic is deprecated) assembly is not part of the framework, but can be installed from nuget: System.Linq.Dynamic.Core
Solution 3
Just stumbled across this question.
Using Marc's ApplyOrder implementation from above, I slapped together an Extension method that handles SQL-like strings like:
list.OrderBy("MyProperty DESC, MyOtherProperty ASC");
Details can be found here: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html
Solution 4
I guess it would work to use reflection to get whatever property you want to sort on:
IEnumerable<T> myEnumerables
var query=from enumerable in myenumerables
where some criteria
orderby GetPropertyValue(enumerable,"SomeProperty")
select enumerable
private static object GetPropertyValue(object obj, string property)
{
System.Reflection.PropertyInfo propertyInfo=obj.GetType().GetProperty(property);
return propertyInfo.GetValue(obj, null);
}
Note that using reflection is considerably slower than accessing the property directly, so the performance would have to be investigated.
Solution 5
Just building on what others have said. I found that the following works quite well.
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> input, string queryString)
{
if (string.IsNullOrEmpty(queryString))
return input;
int i = 0;
foreach (string propname in queryString.Split(','))
{
var subContent = propname.Split('|');
if (Convert.ToInt32(subContent[1].Trim()) == 0)
{
if (i == 0)
input = input.OrderBy(x => GetPropertyValue(x, subContent[0].Trim()));
else
input = ((IOrderedEnumerable<T>)input).ThenBy(x => GetPropertyValue(x, subContent[0].Trim()));
}
else
{
if (i == 0)
input = input.OrderByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
else
input = ((IOrderedEnumerable<T>)input).ThenByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
}
i++;
}
return input;
}
John Sheehan
John Sheehan Founder, CEO of Runscope Worked at IFTTT and Twilio. Created RestSharp, a simple .NET REST and HTTP client.
Updated on July 08, 2022Comments
-
John Sheehan almost 2 years
I found an example in the VS2008 Examples for Dynamic LINQ that allows you to use a SQL-like string (e.g.
OrderBy("Name, Age DESC"))
for ordering. Unfortunately, the method included only works onIQueryable<T>
. Is there any way to get this functionality onIEnumerable<T>
?