Search code examples
c#linqexpression-trees

Expression Trees: Binary operator not defined for types


I'm using the ExpressionVisitor class to dynamically alter search queries sent to a database. I want to replace a binary Or expression, which is the left operand of an AndAlso expression, with a Contains method but my present code is generating the following error:

The binary operator AndAlso is not defined for the types 'System.Func`2[foo.MV,System.Boolean]' and 'System.Boolean'.

Expression AndAlso documentation suggests that I should be able to provide (Expression, Expression) as arguments to this Binary AndAlso Expression but the code below apparently does not satisfy that requirement for the left operand:

 protected override Expression VisitBinary(BinaryExpression b) {
      if (b.NodeType == ExpressionType.Or) {   
           ParameterExpression vParam = Expression.Parameter(typeof(MV), "mv");
           var mvID = Expression.Property(vParam, "MVID");
           ConstantExpression ftsIDs = Expression.Constant(FtsIds, typeof(List<long>));
           var containsInfo = typeof(List<long>).GetMethod("Contains", new Type[] { typeof(long) });

           var containsExp = Expression.Call(ftsIDs, containsInfo, new Expression[] { mvID });
              return Expression.Lambda<Func<MV, bool>>(containsExp, vParam);
        }
    }

I understand the basic message: the types don't match but I can't see what more I need to do to convert my replacement code to something that works and all the example code I've looked through have scenarios that are too simple.

Update:

Thanks to Antonin. Keywords: "Outside the epxression". vParam is already defined.

The above code is recreating it and the compiler will likely give it a funky name.... and ultimately be a referencing a different instance. What actually needs to be done is harvest the existing vParam expression as below:

 protected override Expression VisitBinary(BinaryExpression b) {
       if (b.NodeType == ExpressionType.Or) { 

            MethodCallExpression mce = this.Visit(b.Left) as MethodCallExpression;    
            MemberExpression mex = mce?.Object as MemberExpression;

            var mvID = Expression.Property(mex.Expression, "MVID"); // <<  Injecting existing param here
            ConstantExpression ftsIDs = Expression.Constant(FtsIds, typeof(List<long>));
            var containsInfo = typeof(List<long>).GetMethod("Contains", new Type[] { typeof(long) });

           var containsExp = Expression.Call(ftsIDs, containsInfo, new Expression[] { mvID });
           return containsExp;
        }
    }

As mex.Expression contains the reference to the original ParameterExpression that the original code was essentially recreating via vParam. So much easier when you look down instead of up ;)


Solution

  • You should return containsExp directly, passing Expression.Lambda as an argument for And does not make sense. And can operate on boolean values. It can operate on result of function, if the result is boolean value. But it can not take function itself as an argument.

    what you have done is basically

    bool res = ((Func<MV, bool>)(mv => FtsIds.Contains(mv.MVID))) && true;