Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF orderby / thenby combo extension method

I want to be able to apply combo firstby / thenby sorting as follows:

allOrders().sort(s => s.ProductName, s => s.OrderDate)

So using this post as inspiration, I wrote this extension method, which compiles fine:

public static IQueryable<T> sort<T>(this IQueryable<T> entities, params 
  Expression<Func<T, object>>[] predicates) where T : class 
{
  var sorted = entities.OrderBy(predicates[0]);
  for (int i = 1; i < predicates.Length; i++)
    sorted = sorted.ThenBy(predicates[i]);

  return sorted;
}

And I also tried this succint version, which also compiles:

public static IQueryable<T> sort<T>(this IQueryable<T> entities, params 
  Expression<Func<T, object>>[] predicates) where T : class
{
  return predicates.Skip(1).Aggregate(
    entities.OrderBy(predicates[0]),
    (aggregate, currentPredicate) => aggregate.ThenBy(currentPredicate));
}

However if I try sort by a DateTime, I get this exception:

Unable to cast the type 'System.DateTime' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.

What am I doing wrong? I am using EF5.

like image 912
Bobby B Avatar asked Oct 24 '12 20:10

Bobby B


1 Answers

When you return a value type (such as int or DateTime) from a lambda expression that returns object, the compiler generates a Box() call to turn the value type into a boxed object.

The Entity Framework expression parser can't handle this box expression.
The only solution is to pass a strongly-typed lambda expression that returns the value type.

To do that, you can mis-use collection initializers:

public class OrderingCollection<TEntity> : IEnumerable {
    public void Add<TProperty>(Expression<Func<TEntity, TProperty>>) {
        ...
    }
}

public static IQueryable<T> Sort<T>(this IQueryable<T> entities, 
                                    OrderingCollection<T> o) where T : class {
    ...
}


q = q.Sort(new OrderingCollection { s => s.ProductName, s => s.OrderDate });

Collection initializers allow you to use type inference with an arbitrary number of different type parameters.

like image 150
SLaks Avatar answered Sep 28 '22 07:09

SLaks