Search code examples
c#expressionpredicatelinq-expressionspredicatebuilder

Combine two predicates of different type


I have built a predicate by the Address model (Expression<Func<Address, bool>> addressPred). Now i want to combine this with a predicate of the Person model (Expression<Func<Person, bool>> personPred). Example of the models are:

public class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }
}
public class Address
{
    public string StreetName { get; set; }
}

If I do something like

addressPred = addressPred.And(a => a.StreetName == "Foo bar");

and then combine it with the personPred, the combined predicate will have a statement equalent to

combinedPred = combinedPred.And(a => a.Address.StreetName == "Foo bar");

The combinedPredicate is of type Person.
How can I achive this?

Edit: In reality the models are alot bigger and are shortened for simplicity's sake. What I ultimately want to achive is to build the Address predicate once, then build a predicate for Person with its own conditions and then combine it into a predicate where the Address-part of the Person predicate comes from the Address predicate (Address is a property of Person). The reason I want to do it this way is because Address may have alot of conditions, and I want to use it as a part of other predicates (Person, later also Company, Customer etc) before making a db-call (to Person, Company, Customer etc)
Also the combinedPred line of code was only to show what the equalent statement of the combinedPred would be like coming from Address predicate.


Solution

  • The situation is that the built Expression<Func<Address, bool>> addressPred has an address as ParameterExpression and it is requred to apply the expression to a MemberExpression. So I can suggest to replace the ParameterExpression with the MemberExpression like that:

    var personParameter = Expression.Parameter(typeof(Person), "person");
    var addressProperty = Expression.PropertyOrField(personParameter, "Address");        
    var combined = new ReplaceVisitor<Address>(addressProperty).Visit(addressPred.Body);
    var result = Expression.Lambda<Func<Person, bool>>(combined, personParameter);
    

    Where:

    public class ReplaceVisitor<T> : ExpressionVisitor
    {
        private readonly MemberExpression _replacement;
    
        public ReplaceVisitor(MemberExpression replacement)
        {
            _replacement = replacement;
        }
    
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return node.Type.IsAssignableFrom(typeof(T))
                    ? _replacement : base.VisitParameter(node);
        }
    }