Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Append to an expression

Tags:

c#

linq

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?

like image 883
user38230 Avatar asked Feb 09 '10 18:02

user38230


2 Answers

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.

like image 138
Eric Lippert Avatar answered Nov 12 '22 07:11

Eric Lippert


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);
            }
        }
    }
}
like image 25
Dave Welling Avatar answered Nov 12 '22 07:11

Dave Welling