Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Troubles with executing LambdaExpression

H. I'm trying to construct linq query that dynamicly generate query for custom ordering for dynamicly sent field.

I'm construction logic of

Expression<Func<string, int>> SpaceStringSortExpression = (a) => a.StartsWith(" ") ? 2 : 1; 

Signature of this code (SpaceStringSortExpression.ToString()) is "a => IIF(a.StartsWith(\" \"), 2, 1)"

To do that dynamicly I've made:

ParameterExpression parameter = Expression.Parameter(typeof(TSource), "p1");
            Expression orderByProperty = Expression.Property(parameter, propertyName);
            ConstantExpression c = Expression.Constant(" ", typeof(string));
            MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
            Expression call = Expression.Call(orderByProperty, mi, c);
            Expression<Func<TSource, bool>> lambda = Expression.Lambda<Func<TSource, bool>>(call, parameter);
            ConditionalExpression t = Expression.IfThenElse(call, Expression.Constant(2), Expression.Constant(1));
            //t.tostring() - IIF(p1.Login.StartsWith(" "), 2, 1)
            LambdaExpression callt = Expression.Lambda(t, new[] { parameter });
            //callt.tostring() = p1 => IIF(p1.Login.StartsWith(" "), 2, 1)

But I can't make it work passing the result to OrderBy

MethodInfo genericMethod;

            genericMethod = OrderByMethod.MakeGenericMethod

            genericMethod = OrderByDescendingMethod.MakeGenericMethod
           (new[] { typeof(TSource), typeof(Int32) });

        object ret = genericMethod.Invoke(null, new object[] { source, callt });
        return (IQueryable<TSource>)ret;

And I've got (translated from locolised IIS)

Unable to convert "System.Linq.Expressions.Expression`1[System.Action`1[XXXX.User]]" to type "System.Linq.Expressions.Expression`1[System.Func`2[XXXX.User,System.Int32]]".

The whole code is:

public static IQueryable<TSource> OrderByPropertyRegardingWhiteSpaces<TSource>
        (this IQueryable<TSource> source, string propertyName, bool ascDirection = true)
        {
  ParameterExpression parameter = Expression.Parameter(typeof(TSource), "p1");
            Expression orderByProperty = Expression.Property(parameter, propertyName);
            ConstantExpression c = Expression.Constant(" ", typeof(string));
            MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
            Expression call = Expression.Call(orderByProperty, mi, c);
            Expression<Func<TSource, bool>> lambda = Expression.Lambda<Func<TSource, bool>>(call, parameter);
            ConditionalExpression t = Expression.IfThenElse(call, Expression.Constant(2), Expression.Constant(1));
            //t.tostring() - IIF(p1.Login.StartsWith(" "), 2, 1)
            LambdaExpression callt = Expression.Lambda(t, new[] { parameter });
            //callt.tostring() = p1 => IIF(p1.Login.StartsWith(" "), 2, 1)
MethodInfo genericMethod;
genericMethod = OrderByMethod.MakeGenericMethod
                (new[] { typeof(TSource), typeof(Int32) });
object ret = genericMethod.Invoke(null, new object[] { source, callt });
            return (IQueryable<TSource>)ret;
   }



        private static readonly MethodInfo OrderByMethod =
        typeof(Queryable).GetMethods()
            .Where(method => method.Name == "OrderBy")
            .Where(method => method.GetParameters().Length == 2)
            .Single();

Could anyone help me with this?

the simplier example (just sort by dynamic parametr) is working fine:

public static IQueryable<TSource> OrderByProperty<TSource>
        (this IQueryable<TSource> source, string propertyName, bool ascDirection = true)
        {
            ParameterExpression parameter = Expression.Parameter(typeof(TSource), "p1");
            Expression orderByProperty = Expression.Property(parameter, propertyName);

            LambdaExpression call = Expression.Lambda(orderByProperty, new[] { parameter });
MethodInfo genericMethod;
            if (ascDirection)
            {
                genericMethod = OrderByMethod.MakeGenericMethod
                (new[] { typeof(TSource), orderByProperty.Type });
            }
            else
            {
                genericMethod = OrderByDescendingMethod.MakeGenericMethod
               (new[] { typeof(TSource), orderByProperty.Type });
            }
            object ret = genericMethod.Invoke(null, new object[] { source, call });
            return (IQueryable<TSource>)ret;
        }
like image 234
Sergey Novikov Avatar asked Jan 01 '26 05:01

Sergey Novikov


1 Answers

The problem is that this:

ConditionalExpression t = Expression.IfThenElse(call, Expression.Constant(2), Expression.Constant(1));

becomes an expression of type Action, the expressions for "then" and "else" are executed but do not return a value as you intended. This doesn't match the signature desired by the OrderBy method, so it throws the exception you are seeing. You want this:

ConditionalExpression t = Expression.Condition(call, Expression.Constant(2), Expression.Constant(1));

The Condition node type has a return value. A great way to debug this is to write a unit test that does this:

Expression<Func<bool,int>> expr = (a) => a ? 2 : 1

and then use the debugger to examine the expression tree that was generated. Hope that helps.

like image 108
nslowes Avatar answered Jan 05 '26 20:01

nslowes



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!