Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>

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 on IQueryable<T>. Is there any way to get this functionality on IEnumerable<T>?

like image 681
John Sheehan Avatar asked Sep 03 '08 06:09

John Sheehan


People also ask

What is OrderBy in LINQ?

Sorts the elements of a sequence in ascending order according to a key. OrderBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>, IComparer<TKey>) Sorts the elements of a sequence in ascending order by using a specified comparer.

What is Dynamic LINQ?

The Dynamic LINQ library exposes a set of extension methods on IQueryable corresponding to the standard LINQ methods at Queryable, and which accept strings in a special syntax instead of expression trees.

What is difference between OrderBy and ThenBy in LINQ?

Generally, ThenBy method is used with the OrderBy method. The OrderBy() Method, first sort the elements of the sequence or collection in ascending order after that ThenBy() method is used to again sort the result of OrderBy() method in ascending order.

What algorithm does LINQ OrderBy use?

For LINQ to Objects, it's a stable quicksort that is used.


2 Answers

Too easy without any complication:

  1. Add using System.Linq.Dynamic; at the top.
  2. 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

like image 42
Alaa Osta Avatar answered Sep 20 '22 21:09

Alaa Osta


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);         }     } } 
like image 147
Marc Gravell Avatar answered Sep 21 '22 21:09

Marc Gravell