Use lambda expression in another lambda expression
Asked Answered
A

2

6

I need to combine two lambda expressions, in a way that the second expression "contains" the first. I searched a lot but did not find any clear answer...

What i am trying to do is the following : A first expression "expression1" is passed as a parameter to a method, and is simply used to define on which field or property the second lambda must operate.

Schematically, I am trying to do the following :

// simple field selector :
Expression<Func<T, string>> expression1 = obj => obj.field; 

// trying to use the field selector in 2nd expression :
Expression<Func<T, bool>> e2 = obj => [[Result of expression1]].Equals("myValue");

In other words, I would like to get :

Expression<Func<T, bool>> e2 = obj => obj.field.Equals("myValue"); 

I need to do it this way because it is not always the Equals() method that will be called, but many different methods.

I tried to compile expression1 to a Func<T, string> in order to invoke this Func in expression2, but as I am using this with LinQ, I get an exception because LinQ does not support Invoke node types.

So my question is : is there a way to just combine the bodies of the two expressions, and how please ?

Thanks by advance !

Amble answered 17/10, 2014 at 10:37 Comment(0)
S
5

Well, this should do the trick:

void Main()
{
    var execute = Create<TestClass>(
            first => first.MyStringField, // field selector
            second => second.Equals("1234") // method selector
        );

    Console.WriteLine(execute(new TestClass("1234"))); // true
    Console.WriteLine(execute(new TestClass("4321"))); // false
}

class TestClass
{
    public string MyStringField;

    public TestClass(string val){
        MyStringField = val;
    }

}

static Func<TSource, bool> Create<TSource>(
                    Expression<Func<TSource, string>> fieldSelector,
                    Expression<Func<string, bool>> methodSelector
                )
{
    // todo: classical validation of fieldSelector, if necessary.
    // todo: classical validation of methodSelector, if necessary.

    var compiledFieldSelector = fieldSelector.Compile();
    var compiledMethodSelector = methodSelector.Compile();

    return T => compiledMethodSelector(compiledFieldSelector(T));
}

Note, due the nature of lambda expressions, you need to validate the field selector and method selector, otherwise it's possible to do some very weird things.

Alternatively, the next approach creates lambda that does not "compose" things, in the sense, this is better since LinqToEntities shouldn't have problems interpreting the query.

 static Func<TSource, bool> Create<TSource>(
                    Expression<Func<TSource, string>> memberSelector,
                    Expression<Func<string, bool>> methodSelector
                )
{
    // todo: classical validation of memberSelector, if necessary.
    // todo: classical validation of methodSelector, if necessary.

    var memberExpression = (MemberExpression)(memberSelector.Body);
    var methodCallExpression = (MethodCallExpression)(methodSelector.Body);

    // input TSource => ..
    var input = Expression.Parameter(typeof(TSource));

    var call = Expression.Call(
                Expression.MakeMemberAccess(
                                input, 
                                memberExpression.Member), 

                methodCallExpression.Method, 
                methodCallExpression.Arguments);

    return Expression.Lambda<Func<TSource, bool>>(call, input)
                .Compile();
}
Sarge answered 17/10, 2014 at 11:14 Comment(3)
Thank you Chris Eelmaa. Your solution works great in a classical context. Unfortunately as I am using LinQ, I get the following System.NotSupportedException at at runtime : The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.Amble
@Numer_11: Updated post. Is this what you're looking for?Sarge
Yes ! I tested your new answer but still got the exception. After that I removed the .Compile() call, and changed the return type of the Create method to match. Finally with this little modification it works perfectly with LinQ ! thanks a lot ! (posting the modification to make it clear for others)Amble
A
2

Chris Eelmaa's 2nd answer is ok, just remove the .Compile() call to avoid having the Exception with Invoke :

static Expression<Func<TSource, bool>> Create<TSource>(
                    Expression<Func<TSource, string>> memberSelector,
                    Expression<Func<string, bool>> methodSelector
                )
{
    // todo: classical validation of memberSelector, if necessary.
    // todo: classical validation of methodSelector, if necessary.

    var memberExpression = (MemberExpression)(memberSelector.Body);
    var methodCallExpression = (MethodCallExpression)(methodSelector.Body);

    // input TSource => ..
    var input = Expression.Parameter(typeof(TSource));

    var call = Expression.Call(
                Expression.MakeMemberAccess(
                                input, 
                                memberExpression.Member), 

                methodCallExpression.Method, 
                methodCallExpression.Arguments);

    return Expression.Lambda<Func<TSource, bool>>(call, input);
}

In my case this is then used like this : (selector is an expression like u => u.id, and request is a IQueryable<T>, both received as arguments)

Expression<Func<T, bool>> contains = Create<T>(selector, u => u.Contains(searchExpression));
IQueryable<T> result = request.Where(contains);
Amble answered 17/10, 2014 at 15:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.