Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use lambda expression in another lambda expression

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 !

like image 730
Numer_11 Avatar asked Oct 17 '14 10:10

Numer_11


2 Answers

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();
}
like image 82
Erti-Chris Eelmaa Avatar answered Oct 13 '22 22:10

Erti-Chris Eelmaa


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);
like image 41
Numer_11 Avatar answered Oct 13 '22 22:10

Numer_11