2

This is a follow up to my question Dynamic Expression Generation Issues with ValueTypes adding in a new variable: Entity Framework. Now that I am able to generate the necessary Expressions when dealing with ValueTypes, I'm running into a new issue when Linq-to-Entities attempts to process the query. I receive the following error:

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

So apparently Linq-to-Entities is not a fan of boxed values. This works when I force the query to process (via ToList() or another type method), but not when it's done by the database, which would be ideal.

Is there a way to make this method more generic to make Linq-to-Entities happy? Keep in mind that I don't know the type of the property until runtime.

public IEnumerable<Expression<Func<T, object>>> GetExpressions<T>(string sortedColumn) where T : IReportRecord
{
    var columns = GetFullSortOrder(sortedColumn);
    var typeParameter = Expression.Parameter(typeof(T));
    foreach (var c in columns)
    {
        var propParameter = Expression.Property(typeParameter, c);
        if (propParameter.Type.IsValueType)
        {
            var boxedPropParameter = Expression.Convert(propParameter, typeof(object));
            yield return Expression.Lambda<Func<T, object>>(boxedPropParameter, typeParameter);
        }
        else
        {
            yield return Expression.Lambda<Func<T, object>>(propParameter, typeParameter);
        }
    }
}
Community
  • 1
  • 1
JNYRanger
  • 6,829
  • 12
  • 53
  • 81
  • 1
    Instead of generating `Expressions` you can apply your ordering directly to the `IQueryable` dynamically like this: http://stackoverflow.com/questions/41244/dynamic-linq-orderby-on-ienumerablet/233505#233505 – Aducci Feb 03 '17 at 22:11

1 Answers1

2

Actually this issue is the opposite of the previous where you needed Expression.Convert when generating Expression<Func<T, object>>. Consider a method with the following signature

public static IOrderedQueryable<T> OrderBy<T>(
    this IQueryable<T> source,
    IEnumerable<Expression<Func<T, object>>> selectors)

which has to create OrderBy / ThenBy chain from the passed selectors. Here you need to remove the Expression.Convert needed only to make possible converting Expression<Func<T, V>> to Expression<Func<T, object>> for value type V.

Let create a small helper methods for both conversions:

public static Expression Wrap(this Expression source)
{
    if (source.Type.IsValueType)
        return Expression.Convert(source, typeof(object));
    return source;
}

public static LambdaExpression Unwrap<T>(this Expression<Func<T, object>> source)
{
    var body = source.Body;
    if (body.NodeType == ExpressionType.Convert)
        body = ((UnaryExpression)body).Operand;
    return Expression.Lambda(body, source.Parameters);
}

Now the implementation of the original method could be simply

public static IEnumerable<Expression<Func<T, object>>> GetExpressions<T>(string sortedColumn) where T : IReportRecord
{
    var typeParameter = Expression.Parameter(typeof(T));
    return from c in GetFullSortOrder(sortedColumn)
           select Expression.Lambda<Func<T, object>>(
                Expression.Property(typeParameter, c).Wrap(), typeParameter);
}

and the applying method could be something like this

public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, IEnumerable<Expression<Func<T, object>>> keySelectors)
{
    var result = source.Expression;
    var method = "OrderBy";
    foreach (var item in keySelectors)
    {
        var keySelector = item.Unwrap();
        result = Expression.Call(
            typeof(Queryable), method, new[] { typeof(T), keySelector.Body.Type },
            result, Expression.Quote(keySelector));
        method = "ThenBy";
    }
    return (IOrderedQueryable<T>)source.Provider.CreateQuery(result);
}

Of course it doesn't need to be that way. In your case you could combine the two methods in one (similar to the second, but receiving string sortedColumn), in which case you will simply use the non generic Expression.Lambda method without wrapping the value types with Convert.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • 1
    This is exactly what I needed, perfect! Thanks Ivan! The only difference is that instead of using the extension methods I just had the `GetExpressions` method return the non-generic version as you suggested. Works swimmingly! – JNYRanger Feb 06 '17 at 16:11