Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ to Entities OrderBy Expression Tree

I am trying to write a LINQ query to orderBy a dynamic property given by a string value.

Here is what my original code was:

 Expression<Func<T, dynamic>> orderBy = i => i.GetType().GetProperty("PropertyName").GetValue(null);

When I tried to run this orderBy I got the following exception:

LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object)' method, and this method cannot be translated into a store expression.

I am trying to work around this by creating an expression tree that will give me the same result. The code should be able to return any type depending on the parameter but I'm having trouble with the return type. If I don't convert the value I get a different error saying w Nullable DateTime can't be converted to Object. Here is the code that I have so far:

ParameterExpression pe = Expression.Parameter(typeof(T), "s");
Expression<Func<T, dynamic>> orderByExpression = Expression.Lambda<Func<T, dynamic>>(Expression.Convert(Expression.Property(pe, "PropertyName"), typeof(object)), pe);

and my new exception:

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

How would I write this expression tree to do return a dynamic type? Also is there a better way I should be doing this in LINQ?

like image 443
Seth Denburg Avatar asked Jan 12 '16 23:01

Seth Denburg


1 Answers

There are no template arguments for Expression<Func<T, TT>> which will allow you to represent both reference types and value types. Of course, you can build the expressions, but you must interact with them via reflection.

This will properly sort a collection:

IOrderedEnumerable<TEntityType> SortMeDynamically<TEntityType>(IEnumerable<TEntityType> query, string propertyname)
{
    var param = Expression.Parameter(typeof(TEntityType), "s");
    var prop = Expression.PropertyOrField(param, propertyname);
    var sortLambda = Expression.Lambda(prop, param);

    Expression<Func<IOrderedEnumerable<TEntityType>>> sortMethod = (() => query.OrderBy<TEntityType, object>(k => null));

    var methodCallExpression = (sortMethod.Body as MethodCallExpression);
    if (methodCallExpression == null)
        throw new Exception("Oops");

    var method = methodCallExpression.Method.GetGenericMethodDefinition();
    var genericSortMethod = method.MakeGenericMethod(typeof(TEntityType), prop.Type);
    var orderedQuery = (IOrderedEnumerable<TEntityType>)genericSortMethod.Invoke(query, new object[] { query, sortLambda.Compile() });

    return orderedQuery;
}

Or if you want it on an IQueryable (if you're using EF, for example)

IOrderedQueryable<TEntityType> SortMeDynamically<TEntityType>(IQueryable<TEntityType> query, string propertyname)
{
    var param = Expression.Parameter(typeof(TEntityType), "s");
    var prop = Expression.PropertyOrField(param, propertyname);
    var sortLambda = Expression.Lambda(prop, param);

    Expression<Func<IOrderedQueryable<TEntityType>>> sortMethod = (() => query.OrderBy<TEntityType, object>(k => null));

    var methodCallExpression = (sortMethod.Body as MethodCallExpression);
    if (methodCallExpression == null)
        throw new Exception("Oops");

    var method = methodCallExpression.Method.GetGenericMethodDefinition();
    var genericSortMethod = method.MakeGenericMethod(typeof(TEntityType), prop.Type);
    var orderedQuery = (IOrderedQueryable<TEntityType>)genericSortMethod.Invoke(query, new object[] { query, sortLambda });

    return orderedQuery;
}
like image 173
Rob Avatar answered Oct 11 '22 11:10

Rob