I agree with the OP. I had the same StackOverflowException using the BuildContainsExpression method, which was published by many people (my expression had 6000 OR). I modified BuildContainsExpression to create a balanced tree (depth = O (log (N))). In case this 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();
if (!values.Any())
{
return e => false;
}
var equals = values.Select(
value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));
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 GetOrExpr, which replaces the use of Aggregate in the original version. GetOrExpr recursively splits the predicate list in half to create the "left side" and the "right side", and then creates an expression (lhs OR rhs). A usage example 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))))