StackOverflowException в LINQ to SQL - PullRequest
3 голосов
/ 13 октября 2009

Мы используем LINQ to SQL для работы с базой данных в нашем проекте, и почти все в порядке, но одно: иногда нам приходится создавать огромное условие WHERE, используя некоторый универсальный объект запроса, который создается с помощью пользовательского ввода.

Чтобы построить предикат для включения в выражение WHERE, мы использовали описанные здесь приемы http://www.albahari.com/nutshell/predicatebuilder.aspx, но выражение построено таким образом делает LINQ to SQL генерирующим StackOverflowException, если предикат WHERE включает слишком много условий (на самом деле несколько сотен) при преобразовании результирующего выражения в запрос SQL.

Есть ли способ построить выражение LINQ с кучей условий, чтобы LINQ to SQL относился к нему хорошо?

Ответы [ 2 ]

3 голосов
/ 11 июня 2010

Я согласен с ОП. У меня было то же исключение StackOverflowException с использованием метода BuildContainsExpression, которое опубликовали многие люди (в моем выражении было 6000 OR). Я изменил BuildContainsExpression для создания сбалансированного дерева (глубина = 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 ИЛИ 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 голос
/ 09 февраля 2010

На вашем месте я бы поигрался с вашим запросом LINQ в LinqPad, чтобы увидеть, что вы можете сделать, чтобы обойти ошибку, у нее очень аккуратный конструктор выражений:

http://www.linqpad.net/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...