I agree with the OP. I had the same StackOverflowException using the BuildContainsExpression method that many people have posted (my expression had 6000 ORs). I modified BuildContainsExpression to produce a balanced tree (depth = O(log(N))). In case it might be useful to someone, here it is:
public static Expression<System.Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
Expression<System.Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
{
if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
if (null == values) { throw new ArgumentNullException("values"); }
ParameterExpression p = valueSelector.Parameters.Single();
// p => valueSelector(p) == values[0] || valueSelector(p) == ...
if (!values.Any())
{
return e => false;
}
var equals = values.Select(
value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));
//The use of ToArray here is very important for performance reasons.
var body = GetOrExpr(equals.ToArray());
return Expression.Lambda<System.Func<TElement, bool>>(body, p);
}
private static Expression GetOrExpr(IEnumerable<Expression> exprList)
{
return GetOrExpr(exprList, 0, exprList.Count() - 1);
}
private static Expression GetOrExpr(IEnumerable<Expression> exprList, int startIndex, int endIndex)
{
if (startIndex == endIndex)
{
return exprList.ElementAt(startIndex);
}
else
{
int lhsStart = startIndex;
int lhsEnd = (startIndex + endIndex - 1) / 2;
int rhsStart = lhsEnd + 1;
int rhsEnd = endIndex;
return Expression.Or(GetOrExpr(exprList, lhsStart, lhsEnd), GetOrExpr(exprList, rhsStart, rhsEnd));
}
}
The key to the change is the GetOrExpr method, which replaces the use of Aggregate in the original version. GetOrExpr recursively splits the list of predicates in half to create a 'left hand side' and 'right hand side' and then creates the expression (lhs OR rhs). An example use would be something like this:
var customerIds = Enumerable.Range(1, 5);
Expression<Func<Customer, bool>> containsExpr = BuildContainsExpression<Customer, int>(c => c.CustomerId, customerIds);
Console.WriteLine(containsExpr);
This generates an expression like this:
c => (((c.CustomerId = 1) Or (c.CustomerId = 2)) Or ((c.CustomerId = 3) Or ((c.CustomerId = 4) Or (c.CustomerId = 5))))