LINQ to SQL中的StackOverflowException

时间:2009-10-13 14:00:59

标签: .net linq-to-sql

我们正在使用LINQ to SQL在我们的项目中使用数据库,几乎一切都很好但有一点:有时我们必须使用由用户输入构建的一些通用查询对象来构建一个巨大的WHERE条件。

要构建谓词以放入WHERE语句,我们使用了这里解释的技巧http://www.albahari.com/nutshell/predicatebuilder.aspx但是以这种方式构建的表达式  如果WHERE谓词在将结果表达式转换为SQL查询时包含太多条件(实际上有几百个),则会使LINQ to SQL抛出StackOverflowException。

有没有办法用一堆条件构建LINQ表达式,以便LINQ to SQL能很好地处理它?<​​/ p>

2 个答案:

答案 0 :(得分:3)

我同意OP。我使用许多人发布的BuildContainsExpression方法得到了相同的StackOverflowException(我的表达式有6000个OR)。我修改了BuildContainsExpression以生成平衡树(depth = O(log(N)))。如果它可能对某人有用,那么它是:

 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));
        }
    }

更改的关键是GetOrExpr方法,它取代了原始版本中Aggregate的使用。 GetOrExpr递归地将谓词列表分成两半以创建“左手侧”和“右手侧”,然后创建表达式(lhs OR rhs)。一个示例用法是这样的:

var customerIds = Enumerable.Range(1, 5);

Expression<Func<Customer, bool>> containsExpr = BuildContainsExpression<Customer, int>(c => c.CustomerId, customerIds);
Console.WriteLine(containsExpr);

这会生成如下表达式:

c => (((c.CustomerId = 1) Or (c.CustomerId = 2)) Or ((c.CustomerId = 3) Or ((c.CustomerId = 4) Or (c.CustomerId = 5))))

答案 1 :(得分:1)

如果我是你,我会在LinqPad中使用您的LINQ查询来查看您可以做些什么来解决错误,它有一个非常整洁的表达式构建器:

http://www.linqpad.net/