LinQ to SQL выдает исключение Stackoverflow при использовании Any () - PullRequest
0 голосов
/ 06 февраля 2020

Я использую запрос LinQ, который выглядит следующим образом

public List<TEntity> GetEntities<TEntity>(int[] ids)
{
    var someDbSet = new DbSet<TEntity>();

    var resultQ = someDbSet.Where(t => !ids.Any() || ids.Contains(t.ID)); //<= crashing line

    return resultQ.toList();
}

Обычно он работает, но в некоторых случаях, когда размер идентификатора составляет ~ 7000 элементов, происходит сбой. Брошено исключение: «Исключение типа« System.StackOverflowException »было сгенерировано». У него нет трассировки стека или InnerException.

Я также получаю эту информацию: "EntityFramework.pdb не загружен ... содержит отладочную информацию, необходимую для поиска источника для модуля EntityFramework.dll"

Это известная ошибка или кто-то может объяснить, почему она не работает, когда массив больше?

Я использую. NET Framework 4.5, EntityFramework 6.1.3, EntityFramework6.Npg sql 3.0.3

Ответы [ 3 ]

1 голос
/ 06 февраля 2020

Если мы передадим массив только с двумя значениями int[] ids = {1, 2} в ваш метод GetEntities EntityFramework сгенерирует следующий запрос:

SELECT 
[Extent1].[Id] AS [Id], 
...
FROM [dbo].[Entity] AS [Extent1]
WHERE ( NOT EXISTS (SELECT 
    1 AS [C1]
    FROM  (SELECT 
        1 AS [C0]
        FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
    UNION ALL
        SELECT 
        1 AS [C0]
        FROM  ( SELECT 1 AS X ) AS [SingleRowTable2]) AS [UnionAll1]
)) OR (1 = [Extent1].[Id]) OR (2 = [Extent1].[Id])

Если мы увеличим количество элементов в массиве ids, это запрос становится более сложным с большим количеством уровней вложенности. Я думаю, что EntityFramework использует некоторый рекурсивный алгоритм для генерации SQL -кода для !ids.Any() выражения. Когда количество элементов в массиве ids увеличивается, глубина рекурсии также увеличивается. Поэтому он генерирует StackOverflowException, когда число элементов в массиве ids (а также глубина рекурсии) велико.

Если мы удалим выражение !ids.Any(), будет сгенерирован следующий запрос:

SELECT 
[Extent1].[Id] AS [Id], 
...
FROM [dbo].[Entity] AS [Extent1]
WHERE [Extent1].[Id] IN (1,2) 

Такой запрос не генерирует StackOverflowException, когда количество элементов в массиве ids велико. Поэтому было бы лучше извлечь выражение !ids.Any() из запроса LINQ:

public List<TEntity> GetEntities<TEntity>(int[] ids)
{
    var someDbSet = new DbSet<TEntity>();

    if (!ids.Any())
        return someDbSet.ToList();

    var resultQ = someDbSet.Where(t => ids.Contains(t.ID));
    return resultQ.toList();
}

Следует также учитывать, что существует ограничение на количество элементов для условия WHERE IN: Limit на условии WHERE col IN (...) .


ionutnespus писал:

Да, извлечение условия вне Where () работает. Тем не менее, я не смог найти никакого объяснения, почему EF будет использовать такой сложный алгоритм для такого простого условия. Есть какие-нибудь мысли по этому поводу?

Я решил ответить на этот вопрос, расширив этот пост, потому что ответ большой и содержит код.

Не знаю точно, почему EF генерирует такой сложный запрос, но я провел некоторое исследование, и вот мои мысли. Если мы изменим ваш метод GetEntites и используем следующее условие в запросе LINQ:

someDbSet.Where(t => !ids.Any(i => i == 3) || ids.Contains(t.ID));

, будет сгенерирован следующий запрос SQL, если ids = {1, 2}:

SELECT 
[Extent1].[Id] AS [Id], 
...
FROM [dbo].[Entity] AS [Extent1]
WHERE ( NOT EXISTS (
    SELECT 1 AS [C1]
    FROM (
        SELECT 1 AS [C0] FROM  ( SELECT 1 AS X ) AS [SingleRowTable1] WHERE 3 = 1
        UNION ALL
        SELECT 1 AS [C0] FROM  ( SELECT 1 AS X ) AS [SingleRowTable2] WHERE 3 = 2
        ) AS [UnionAll1]
)) OR (1 = [Extent1].[Id]) OR (2 = [Extent1].[Id])

Здесь Вы можете видеть, что условие NOT EXISTS содержит два подзапроса, каждый из которых проверяет, равен ли следующий элемент массива ids требуемому значению. Я думаю, что логично использовать NOT EXISTS SQL -условие для представления Any() метода. Но почему EF генерирует один подзапрос для каждого элемента массива? По моему мнению, EF делает это потому, что EF Team пыталась написать алгоритм, который генерирует запросы, которые не зависят от типа базы данных. Но это только мое мнение. Может быть, лучше задать этот вопрос EF Team на github .

0 голосов
/ 06 февраля 2020

Можешь попробовать вот так?

    public List<TEntity> GetEntities<TEntity>(int[] ids)
    {
        var someDbSet = new DbSet<TEntity>();
        var resultQ = new List<your_list_type>();
        foreach( var id in ids) {
          resultQ.Add(someDbSet.Where(prm => prm.ID == id).FirstOrDefault());
}
        return resultQ;

    }
0 голосов
/ 06 февраля 2020

Согласно вашему сообщению об ошибке, исключение выдается, когда стек выполнения переполняется, потому что он содержит слишком много вложенных вызовов методов. Как MSDN

Максимальный размер по умолчанию 2 гигабайта (ГБ) массива.

В 64-разрядной среде вы можете обойти ограничение размера, установив атрибут enabled элемента конфигурации gcAllowVeryLargeObjects Значение true в среде выполнения.

Более того, ваши идентификаторы превышают пределы 2 ГБ. я думаю, что это может быть причиной

...