Search code examples
c#.net-4.0expression-trees

Expression Tree: iterate through strings and check if they contained in another Expression


I want a function Expression> AnyColumnContains(string[] value) that iterates through all Columns of a table and checks an array of values against the columns and returns true only if every value is contained in any column.

i already have a function that matches every column against one value but i have problems extending it to check the columns against every value

This is what i've got:

Expression<Func<T, bool>> AnyColumnContains<T>(string value){
    var p = Expression.Parameter(typeof(T), "entity");

    var fieldAccessors = typeof(T)
        .GetProperties(BindingFlags.Instance | BindingFlags.Public)
        .Where(f => f.PropertyType == typeof(string))
        .Select(f => Expression.Property(p, f))
        .ToArray();

    var fieldArray = Expression.NewArrayInit(typeof(string), fieldAccessors);

    var concatCall = Expression.Call(typeof(string).GetMethod(
         "Concat", new[] { typeof(string[]) }), fieldArray);

    var contains = Expression.Call(
        concatCall,
        typeof(string).GetMethod("Contains", new[] { typeof(string) }),
        Expression.Constant(value));

    return Expression.Lambda<Func<T, bool>>(contains, p);
}

I tried to use a own extension method and replaced Contains with it but the problem is that i use sqlite and the expression cannot be converted since the Provider doesn't know the methods

This is what i want:

Expression<Func<T, bool>> AnyColumnContains<T>(string[] values){
    // ... //

    var contains  = // build Expression Tree that matches all values against concatCall and only returns true if all values are contained.

    return Expression.Lambda<Func<T, bool>>(contains, p);
}

Solution

  • Rather than making an entirely new method from scratch, you can simply compose the method that you already have that's working.

    We can use the following method to combine predicates together:

    public static class PredicateBuilder
    {
        public static Expression<Func<T, bool>> True<T>() { return f => true; }
        public static Expression<Func<T, bool>> False<T>() { return f => false; }
    
        public static Expression<Func<T, bool>> Or<T>(
            this Expression<Func<T, bool>> expr1,
            Expression<Func<T, bool>> expr2)
        {
            var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
            return Expression.Lambda<Func<T, bool>>
                  (Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
        }
    
        public static Expression<Func<T, bool>> And<T>(
            this Expression<Func<T, bool>> expr1,
            Expression<Func<T, bool>> expr2)
        {
            var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
            return Expression.Lambda<Func<T, bool>>
                  (Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
        }
    }
    

    It relies on the following method to replace all instance of one expression with another:

    public static Expression Replace(this Expression expression,
        Expression searchEx, Expression replaceEx)
    {
        return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
    }
    
    internal class ReplaceVisitor : ExpressionVisitor
    {
        private readonly Expression from, to;
        public ReplaceVisitor(Expression from, Expression to)
        {
            this.from = from;
            this.to = to;
        }
        public override Expression Visit(Expression node)
        {
            return node == from ? to : base.Visit(node);
        }
    }
    

    Now all we have to do is call the single value version of AnyColumnContains for each value and Or all of the results together:

    public static Expression<Func<T, bool>> AnyColumnContains<T>(IEnumerable<string> values)
    {
        return values.Select(value => AnyColumnContains<T>(value))
            .Aggregate((a, b) => a.Or(b));
    }