StackOverflowException в LINQ to SQL

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

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

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

13.10.2009 14:00:59
2 ОТВЕТА

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

http://www.linqpad.net/

1
9.02.2010 16:03:01
На самом деле, я уже нашел решение: мы немного изменили PredicateBuilder, чтобы он мог получить список предикатов и построить сбалансированное дерево выражений вместо того, чтобы помещать каждый новый предикат в ветку того же дерева.
user189126 13.02.2010 20:15:04

Я согласен с ОП. У меня было то же исключение 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))))
3
11.01.2012 06:47:09
Значение этого поста настолько недооценено ... Это всего лишь третий раз, когда мне нужен этот код. Спасибо
Daniel Leiszen 11.04.2015 19:38:15