17

I followed this thread: link text

Jason gives an example:

public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right)
{
    return Expression.Lambda<TDelegate>(Expression.AndAlso(left, right), left.Parameters);
}

and its usage as such:

Expression<Func<Client, bool>> clientWhere = c => true;
if (filterByClientFName)
{
    clientWhere = clientWhere.AndAlso(c => c.ClientFName == searchForClientFName);
}
if (filterByClientLName)
{
    clientWhere = clientWhere.AndAlso(c => c.ClientLName == searchForClientLName);
}

I have a orders table and i followed the above example, changing column names, and i get the similar error that the post creator had

The binary operator AndAlso is not defined for the types 'System.Func2[Models.Order,System.Boolean]' and 'System.Func2[Models.Order,System.Boolean]'.

Anyone have any thoughts on what I am missing?

UPDATED:

Eric, I further followed what the user of the previous post was asking, here link text

The user has this

Expression<Func<Client, bool>> clientWhere = c => true;
Expression<Func<Order, bool>> orderWhere = o => true;
Expression<Func<Product, bool>> productWhere = p => true;

if (filterByClient)
{
    clientWhere = c => c.ClientID == searchForClientID;
}

Now if he were to have various conditions in filterByClient, say he either has clientid and/or some other column name, how would one build the clientWhere expression?

Community
  • 1
  • 1
user38230
  • 585
  • 3
  • 11
  • 31

4 Answers4

35

You're attempting to build an expression tree that represents this:

c => true && c.ClientFName == searchForClientFName

You are actually building an expression tree that represents this:

c => c=> true && c => c.ClientFName == searchForClientFName

which makes no sense at all.

Now, you might naively think that this will work:

public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right) 
{ 
// NOTICE: Combining BODIES:
    return Expression.Lambda<TDelegate>(Expression.AndAlso(left.Body, right.Body), left.Parameters); 
} 

That would produce in your case something representing

c => true && c.ClientFName == searchForClientFName

Which looks right. But in fact this is fragile. Suppose you had

... d => d.City == "London" ...
... c => c.ClientName == "Fred Smith" ...

and you used this method to combine them. You'd get an object representing

c => d.City == "London" && c.ClientName == "Fred Smith"

What the heck is d doing in there?

Furthermore, parameters are matched by object identity, not by parameter name. If you do this

... c => c.City == "London" ...
... c => c.ClientName == "Fred Smith" ...

and combine them into

c => c.City == "London" && c.ClientName == "Fred Smith"

you're in the same boat; the "c" in "c.City" is a different c than the other two.

What you actually need to do is make a third parameter object, substitute it in the bodies of both lambdas for every occurence of their parameters, and then build up a new lambda expression tree from the resulting substituted bodies.

You can build a substitution engine by writing a visitor that passes over the expression tree body, rewriting it as it goes.

Eric Lippert
  • 612,321
  • 166
  • 1,175
  • 2,033
  • 11
    That sounds like a smart answer for the question why the problem exists, but you haven't really given a solution so that one could benefit from your post... – Michael Ulmann May 04 '11 at 08:12
  • 7
    @Michael: Then I invite you to write an answer that you'd prefer. – Eric Lippert May 04 '11 at 16:19
  • I did something similar to what Eric's suggesting here: http://stackoverflow.com/questions/14248674/system-linq-expressions-binding-lambdaexpression-inputs-at-runtime You may find it useful. – Timothy Shields May 02 '13 at 15:26
  • 1
    would have been a perfect answer if you actually included the substitution engine code instead of telling us that the answer is to write a substitution engine... – Shawn de Wet Jan 27 '17 at 15:28
  • @ShawndeWet: I encourage you to do so then, and post a perfect answer yourself. – Eric Lippert Jan 27 '17 at 16:28
  • Thanks Eric. My experimentation with .AndAlso found that it's worse than just fragile, it doesn't work at all. I stole a trick from [Predicate Builder](http://www.albahari.com/nutshell/predicatebuilder.aspx) to call Expression.Invoke first, which I believe acts as the substitution engine. – Mark Feb 23 '18 at 22:20
23

It was difficult for me to understand hvd's answer so I created some code to explain it in a different way. hvd should get the credit for suggesting the ExpressionVisitor. I just couldn't understand the example in the context of Linq to X type input functions I was using.

I hope this helps somebody else coming to the question from that perspective.

Also, I created the combining code as extension methods to make it a little easier to use.


using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace ConsoleApplication3
{

    class Program
    {

        static void Main(string[] args)
        {

            var combined = TryCombiningExpressions(c => c.FirstName == "Dog", c => c.LastName == "Boy");

            Console.WriteLine("Dog Boy should be true: {0}", combined(new FullName { FirstName = "Dog", LastName = "Boy" }));
            Console.WriteLine("Cat Boy should be false: {0}", combined(new FullName { FirstName = "Cat", LastName = "Boy" }));

            Console.ReadLine();
        }

        public class FullName
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
        }

        public static Func<FullName, bool> TryCombiningExpressions(Expression<Func<FullName, bool>> func1, Expression<Func<FullName, bool>> func2)
        {
            return func1.CombineWithAndAlso(func2).Compile();
        }
    }

    public static class CombineExpressions
    {
        public static Expression<Func<TInput, bool>> CombineWithAndAlso<TInput>(this Expression<Func<TInput, bool>> func1, Expression<Func<TInput, bool>> func2)
        {
            return Expression.Lambda<Func<TInput, bool>>(
                Expression.AndAlso(
                    func1.Body, new ExpressionParameterReplacer(func2.Parameters, func1.Parameters).Visit(func2.Body)),
                func1.Parameters);
        }

        public static Expression<Func<TInput, bool>> CombineWithOrElse<TInput>(this Expression<Func<TInput, bool>> func1, Expression<Func<TInput, bool>> func2)
        {
            return Expression.Lambda<Func<TInput, bool>>(
                Expression.AndAlso(
                    func1.Body, new ExpressionParameterReplacer(func2.Parameters, func1.Parameters).Visit(func2.Body)),
                func1.Parameters);
        }

        private class ExpressionParameterReplacer : ExpressionVisitor
        {
            public ExpressionParameterReplacer(IList<ParameterExpression> fromParameters, IList<ParameterExpression> toParameters)
            {
                ParameterReplacements = new Dictionary<ParameterExpression, ParameterExpression>();
                for (int i = 0; i != fromParameters.Count && i != toParameters.Count; i++)
                    ParameterReplacements.Add(fromParameters[i], toParameters[i]);
            }

            private IDictionary<ParameterExpression, ParameterExpression> ParameterReplacements { get; set; }

            protected override Expression VisitParameter(ParameterExpression node)
            {
                ParameterExpression replacement;
                if (ParameterReplacements.TryGetValue(node, out replacement))
                    node = replacement;
                return base.VisitParameter(node);
            }
        }
    }
}
Community
  • 1
  • 1
Dave Welling
  • 1,210
  • 11
  • 13
  • 5
    I see no diference between `CombineWithAndAlso` and `CombineWithOrElse`. Shouldn't it be `Expresseion.OrElse(..)` in `CombineWithOrElse`? – Undisputed Sep 28 '18 at 10:11
0

If you need it i created a small fluent library to create lambda functions on the fly without directly coping with System.Linq.Expressions. And it can easily handle the kind of situation. Just to give an example:

static void Main(string[] args)
{
  var firstNameCompare = ExpressionUtil.GetComparer<FullName>((a) => a.FirstName);
  var lastNameCompare = ExpressionUtil.GetComparer<FullName>((a) => a.LastName);

  Func<FullName, bool> combined = (a) => firstNameCompare(a, "Dog") && lastNameCompare(a, "Boy");

  var toCheck = new FullName {FirstName = "Dog", LastName = "Boy"};
  Console.WriteLine("Dog Boy should be true: {0}", combined(toCheck));
  toCheck = new FullName {FirstName = "Cat", LastName = "Boy"};
  Console.WriteLine("Cat Boy should be false: {0}", combined(toCheck));

  Console.ReadLine();
}

The GetComparer methods seek for the property passed as expression and find ho to get its value, then it builds a new Expression that will handle the comparaison.

At the end the two functions are evaluated calling the "combined" function.

If you need more verifications you could use an array and iterate on it inside the "combined lambda"

The code and documentation for the library are here: Kendar Expression Builder While the nuget package is here: Nuget Expression Builder

Kendar
  • 658
  • 9
  • 22
0

I tried to implement this kind of stuff. Took me a day to find out. My solution is based on filter in a loop based on a Array of predicate. As a note, it s totally Generic and based Reflection because the only information about class and field are String. To make it simple, i call directly the Model class but in a project you should go by a controler who is calling the Model.

So here we go : The Model part where T is a Generic in the class

    public class DALXmlRepository<T> where T : class
    {
    public T GetItem(Array predicate)
    {
        IQueryable<T> QueryList = null;

        QueryList = ObjectList.AsQueryable<T>().Where((Expression<Func<T, bool>>)predicate.GetValue(0));
        for (int i = 1; i < predicate.GetLength(0); i++)
        {
            QueryList = QueryList.Where((Expression<Func<T, bool>>)predicate.GetValue(i));
        }

        if (QueryList.FirstOrDefault() == null)
            throw new InvalidOperationException(this.GetType().GetGenericArguments().First().Name + " not found.");
        return QueryList.FirstOrDefault();
    }
    }

Now the LambdaExpression Builder, it's a base one(with String type or something else) , you can improve it with more functionnality :

    private static Expression BuildLambdaExpression(Type GenericArgument, string FieldName, string FieldValue)
    {
        LambdaExpression lambda = null;

        Expression Criteria = null;

        Random r = new Random();
        ParameterExpression predParam = Expression.Parameter(GenericArgument, r.Next().ToString());

        if (GenericArgument.GetProperty(FieldName).PropertyType == typeof(string))
        {
            Expression left = Expression.PropertyOrField(predParam, FieldName);
            Expression LefttoUpper = Expression.Call(left, "ToUpper", null, null);
            //Type du champ recherché
            Type propType = GenericArgument.GetProperty(FieldName).PropertyType;
            Expression right = Expression.Constant(FieldValue, propType);
            Expression RighttoUpper = Expression.Call(right, "ToUpper", null, null);
            Criteria = Expression.Equal(LefttoUpper, RighttoUpper);
        }
        else
        {
            Expression left = Expression.PropertyOrField(predParam, FieldName);
            Type propType = GenericArgument.GetProperty(FieldName).PropertyType;
            Expression right = Expression.Constant(Convert.ChangeType(FieldValue, propType), propType);

            Criteria = Expression.Equal(left, right);
        }

        lambda = Expression.Lambda(Criteria, predParam);
        return lambda;
    }

Now the Calling function :

    public static Hashtable GetItemWithFilter(string Entity, XMLContext contextXML, Hashtable FieldsNameToGet, Hashtable FieldFilter)
    {
        //Get the type
        Type type = Type.GetType("JP.Model.BO." + Entity + ", JPModel");
        Type CtrlCommonType = typeof(CtrlCommon<>).MakeGenericType( type );
        //Making an instance DALXmlRepository<xxx> XMLInstance = new DALXmlRepository<xxx>(contextXML);
        ConstructorInfo ci = CtrlCommonType.GetConstructor(new Type[] { typeof(XMLContext), typeof(String) });
        IControleur DalInstance = (IControleur)ci.Invoke(new object[] { contextXML, null });

        //Building the string type Expression<func<T,bool>> to init the array
        Type FuncType = typeof(Func<,>).MakeGenericType( type ,typeof(bool));
        Type ExpressType = typeof(Expression<>).MakeGenericType(FuncType);
        Array lambda = Array.CreateInstance(ExpressType,FieldFilter.Count);

        MethodInfo method = DalInstance.GetType().GetMethod("GetItem", new Type[] { lambda.GetType() });

        if (method == null)
            throw new InvalidOperationException("GetItem(Array) doesn't exist for " + DalInstance.GetType().GetGenericArguments().First().Name);

        int j = 0;
        IDictionaryEnumerator criterias = FieldFilter.GetEnumerator();
        criterias.Reset();
        while (criterias.MoveNext())
        {
            if (!String.IsNullOrEmpty(criterias.Key.ToString()))
            {
                lambda.SetValue(BuildLambdaExpression(type, criterias.Key.ToString(), criterias.Value.ToString()),j);
            }
            else
            {
                throw new JPException(JPException.MessageKey.CONTROLER_PARAMFIELD_EMPTY, "GetItemWithFilter", criterias.Key.ToString());
            }
            j++;
        }

        Object item = method.Invoke(DalInstance, new object[] { lambda });
        }

The argument are : String Entity : Entity class name. XMLContext : it s the unit of work of the repository, argument i use to initialize the Model class Hashtable FieldsNameToGet : Index/value of the list of the field i want to get back Hashtable FieldFilter : the key/Value with FieldName/Content used to make the Lambda expression

Good Luck.

Alan
  • 9
  • 3