Merge two linq expressions
Asked Answered
I

1

6

I have two expressions that are built out at separate times, but need to be merged in order to get an accurate 'grouping' of a where clause. I did try this option, but I am using Entity Framework and it doesn't understand the Invoke function. I have seen some of the ExpressionVisitor alternative but I do not think I have a good enough understanding of what I need to do.

If anyone could please point me in the right direction I would much appreciate it, it feels like it is close to there.

Where Clause 1A (object type Expression<Func<T, bool>>)

{parm => parm.Name.Contains("hat")}

Where Clause 1B (object type LambdaExpression, but can use (Expression<Func<T, bool>>)LambdaExpression )

{parm => parm.Attributes.Any(parm => ((parm.Name == "test") AndAlso (parm.Value == "21")))}

Needed Where Clause

{parm =>
 parm.Name.Contains("hat") (&&/||)
 parm.Attributes.Any(parm => ((parm.Name == "test") AndAlso (parm.Value == "21")))
}

If someone could please help me merge Where Clause 1A and Where Clause 1B, I would be very thankful..

Just an FYI Where() and Any() clause are generic methods obtained from IQueryable, not sure if that matters.

Func<MethodInfo, bool> methodLambda = m => m.Name == "Any" && m.GetParameters().Length == 2;
MethodInfo method = typeof(Queryable).GetMethods().Where(methodLambda).Single().MakeGenericMethod(ActualQueryableType);
    
ParameterExpression parentMember = Expression.Parameter(typeof(T), "parentParm");

 // Create Any() Expression tree and convert it to lambda
MethodCallExpression callAny = Expression.Call(method, member, exp);
LambdaExpression lambdaAny = Expression.Lambda(callAny, param);


var combExp = parentExp.And((Expression<Func<T, bool>>)lambdaAny);
                

MethodCallExpression whereCall = Expression.Call(typeof(Queryable), "Where", new Type[] { query.ElementType }, new Expression[] {
query.Expression,
Expression.Quote(combExp)
});

query = (IQueryable<T>)query.Provider.CreateQuery(whereCall);

The error I get when using Invoke is:

NotSupportedException

The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.

Intranuclear answered 21/3, 2014 at 20:36 Comment(3)
Could you show exactly what kind of exception about Invoke you get?Edwyna
@Edwyna The query provider will just throw an exception saying that Invoke isn't supported. Why would the specific type of exception be relevant?Lura
@Lura Because I'm really surprised that LINQ to SQL does not support Expression.Invoke and I'm wondering if OP isn't using Func.Invoke instead, which I'm pretty sure would fail.Edwyna
L
17

Here's an implementation of PredicateBuilder that doesn't use Invoke:

public static class PredicateBuilder
{
    public static Expression<Func<T, bool>> True<T>() { return f => true; }
    public static Expression<Func<T, bool>> False<T>() { return f => false; }

    public static Expression<Func<T, bool>> Or<T>(
        this Expression<Func<T, bool>> expr1,
        Expression<Func<T, bool>> expr2)
    {
        var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
        return Expression.Lambda<Func<T, bool>>
              (Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
    }

    public static Expression<Func<T, bool>> And<T>(
        this Expression<Func<T, bool>> expr1,
        Expression<Func<T, bool>> expr2)
    {
        var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
        return Expression.Lambda<Func<T, bool>>
              (Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
    }
}

It instead uses a Replace method (implementation below) that replaces all instances of one expression with another.

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

Using this you can now use And to AND together two predicate expressions that take the same input.

Lura answered 21/3, 2014 at 20:38 Comment(1)
{parm => (parm.Name.Contains("hat") AndAlso parm.Attributes.Any(parm => (parm.Value == "21")))} ... You my friend, are amazing, it requires another 4 minutes to promote :D #PUMPEDIntranuclear

© 2022 - 2024 — McMap. All rights reserved.