Combining two expressions (Expression>)

前端 未结 7 1531
无人共我
无人共我 2020-11-22 01:09

I have two expressions of type Expression> and I want to take to OR, AND or NOT of these and get a new expression of the same type

相关标签:
7条回答
  • 2020-11-22 01:40

    If you provider does not support Invoke and you need to combine two expression, you can use an ExpressionVisitor to replace the parameter in the second expression by the parameter in the first expression.

    class ParameterUpdateVisitor : ExpressionVisitor
    {
        private ParameterExpression _oldParameter;
        private ParameterExpression _newParameter;
    
        public ParameterUpdateVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
        {
            _oldParameter = oldParameter;
            _newParameter = newParameter;
        }
    
        protected override Expression VisitParameter(ParameterExpression node)
        {
            if (object.ReferenceEquals(node, _oldParameter))
                return _newParameter;
    
            return base.VisitParameter(node);
        }
    }
    
    static Expression<Func<T, bool>> UpdateParameter<T>(
        Expression<Func<T, bool>> expr,
        ParameterExpression newParameter)
    {
        var visitor = new ParameterUpdateVisitor(expr.Parameters[0], newParameter);
        var body = visitor.Visit(expr.Body);
    
        return Expression.Lambda<Func<T, bool>>(body, newParameter);
    }
    
    [TestMethod]
    public void ExpressionText()
    {
        string text = "test";
    
        Expression<Func<Coco, bool>> expr1 = p => p.Item1.Contains(text);
        Expression<Func<Coco, bool>> expr2 = q => q.Item2.Contains(text);
        Expression<Func<Coco, bool>> expr3 = UpdateParameter(expr2, expr1.Parameters[0]);
    
        var expr4 = Expression.Lambda<Func<Recording, bool>>(
            Expression.OrElse(expr1.Body, expr3.Body), expr1.Parameters[0]);
    
        var func = expr4.Compile();
    
        Assert.IsTrue(func(new Coco { Item1 = "caca", Item2 = "test pipi" }));
    }
    
    0 讨论(0)
  • 2020-11-22 01:41

    I needed to achieve the same results, but using something more generic (as the type was not known). Thanks to marc's answer I finally figured out what I was trying to achieve:

        public static LambdaExpression CombineOr(Type sourceType, LambdaExpression exp, LambdaExpression newExp) 
        {
            var parameter = Expression.Parameter(sourceType);
    
            var leftVisitor = new ReplaceExpressionVisitor(exp.Parameters[0], parameter);
            var left = leftVisitor.Visit(exp.Body);
    
            var rightVisitor = new ReplaceExpressionVisitor(newExp.Parameters[0], parameter);
            var right = rightVisitor.Visit(newExp.Body);
    
            var delegateType = typeof(Func<,>).MakeGenericType(sourceType, typeof(bool));
            return Expression.Lambda(delegateType, Expression.Or(left, right), parameter);
        }
    
    0 讨论(0)
  • 2020-11-22 01:42

    Nothing new here but married this answer with this answer and slightly refactored it so that even I understand what's going on:

    public static class ExpressionExtensions
    {
        public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
        {
            ParameterExpression parameter1 = expr1.Parameters[0];
            var visitor = new ReplaceParameterVisitor(expr2.Parameters[0], parameter1);
            var body2WithParam1 = visitor.Visit(expr2.Body);
            return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, body2WithParam1), parameter1);
        }
    
        private class ReplaceParameterVisitor : ExpressionVisitor
        {
            private ParameterExpression _oldParameter;
            private ParameterExpression _newParameter;
    
            public ReplaceParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
            {
                _oldParameter = oldParameter;
                _newParameter = newParameter;
            }
    
            protected override Expression VisitParameter(ParameterExpression node)
            {
                if (ReferenceEquals(node, _oldParameter))
                    return _newParameter;
    
                return base.VisitParameter(node);
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 01:46

    You can use Expression.AndAlso / OrElse to combine logical expressions, but you have to make sure the ParameterExpressions are the same.

    I was having trouble with EF and the PredicateBuilder so I made my own without resorting to Invoke, that I could use like this:

    var filterC = filterA.And(filterb);
    

    Source code for my PredicateBuilder:

    public static class PredicateBuilder {
    
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {    
    
            ParameterExpression p = a.Parameters[0];
    
            SubstExpressionVisitor visitor = new SubstExpressionVisitor();
            visitor.subst[b.Parameters[0]] = p;
    
            Expression body = Expression.AndAlso(a.Body, visitor.Visit(b.Body));
            return Expression.Lambda<Func<T, bool>>(body, p);
        }
    
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {    
    
            ParameterExpression p = a.Parameters[0];
    
            SubstExpressionVisitor visitor = new SubstExpressionVisitor();
            visitor.subst[b.Parameters[0]] = p;
    
            Expression body = Expression.OrElse(a.Body, visitor.Visit(b.Body));
            return Expression.Lambda<Func<T, bool>>(body, p);
        }   
    }
    

    And the utility class to substitute the parameters in a lambda:

    internal class SubstExpressionVisitor : System.Linq.Expressions.ExpressionVisitor {
            public Dictionary<Expression, Expression> subst = new Dictionary<Expression, Expression>();
    
            protected override Expression VisitParameter(ParameterExpression node) {
                Expression newValue;
                if (subst.TryGetValue(node, out newValue)) {
                    return newValue;
                }
                return node;
            }
        }
    
    0 讨论(0)
  • 2020-11-22 01:56

    I think this works fine, isn't it ?

    Func<T, bool> expr1 = (x => x.Att1 == "a");
    Func<T, bool> expr2 = (x => x.Att2 == "b");
    Func<T, bool> expr1ANDexpr2 = (x => expr1(x) && expr2(x));
    Func<T, bool> expr1ORexpr2 = (x => expr1(x) || expr2(x));
    Func<T, bool> NOTexpr1 = (x => !expr1(x));
    
    0 讨论(0)
  • 2020-11-22 01:57

    Well, you can use Expression.AndAlso / OrElse etc to combine logical expressions, but the problem is the parameters; are you working with the same ParameterExpression in expr1 and expr2? If so, it is easier:

    var body = Expression.AndAlso(expr1.Body, expr2.Body);
    var lambda = Expression.Lambda<Func<T,bool>>(body, expr1.Parameters[0]);
    

    This also works well to negate a single operation:

    static Expression<Func<T, bool>> Not<T>(
        this Expression<Func<T, bool>> expr)
    {
        return Expression.Lambda<Func<T, bool>>(
            Expression.Not(expr.Body), expr.Parameters[0]);
    }
    

    Otherwise, depending on the LINQ provider, you might be able to combine them with Invoke:

    // OrElse is very similar...
    static Expression<Func<T, bool>> AndAlso<T>(
        this Expression<Func<T, bool>> left,
        Expression<Func<T, bool>> right)
    {
        var param = Expression.Parameter(typeof(T), "x");
        var body = Expression.AndAlso(
                Expression.Invoke(left, param),
                Expression.Invoke(right, param)
            );
        var lambda = Expression.Lambda<Func<T, bool>>(body, param);
        return lambda;
    }
    

    Somewhere, I have got some code that re-writes an expression-tree replacing nodes to remove the need for Invoke, but it is quite lengthy (and I can't remember where I left it...)


    Generalized version that picks the simplest route:

    static Expression<Func<T, bool>> AndAlso<T>(
        this Expression<Func<T, bool>> expr1,
        Expression<Func<T, bool>> expr2)
    {
        // need to detect whether they use the same
        // parameter instance; if not, they need fixing
        ParameterExpression param = expr1.Parameters[0];
        if (ReferenceEquals(param, expr2.Parameters[0]))
        {
            // simple version
            return Expression.Lambda<Func<T, bool>>(
                Expression.AndAlso(expr1.Body, expr2.Body), param);
        }
        // otherwise, keep expr1 "as is" and invoke expr2
        return Expression.Lambda<Func<T, bool>>(
            Expression.AndAlso(
                expr1.Body,
                Expression.Invoke(expr2, param)), param);
    }
    

    Starting from .NET 4.0, there is the ExpressionVisitor class which allows you to build expressions that are EF safe.

        public static Expression<Func<T, bool>> AndAlso<T>(
            this Expression<Func<T, bool>> expr1,
            Expression<Func<T, bool>> expr2)
        {
            var parameter = Expression.Parameter(typeof (T));
    
            var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
            var left = leftVisitor.Visit(expr1.Body);
    
            var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
            var right = rightVisitor.Visit(expr2.Body);
    
            return Expression.Lambda<Func<T, bool>>(
                Expression.AndAlso(left, right), parameter);
        }
    
    
    
        private class ReplaceExpressionVisitor
            : ExpressionVisitor
        {
            private readonly Expression _oldValue;
            private readonly Expression _newValue;
    
            public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
            {
                _oldValue = oldValue;
                _newValue = newValue;
            }
    
            public override Expression Visit(Expression node)
            {
                if (node == _oldValue)
                    return _newValue;
                return base.Visit(node);
            }
        }
    
    0 讨论(0)
提交回复
热议问题