@footballer

LINQ в С#: что происходит с Func, который вложен в Expression?

У юзера есть организация, у организации есть юзеры. Юзаем такой код:
_dbContext.Organizations.Where(o => o.Name.Contains("Roga") && o.Users.Any(u => u.FirstName == "Petr")).Select(...)

в организациях ищем только те организации, у которых имя содержит "Roga" и в юзерах есть хотя бы 1 Петр.
Тут у нас две лямбды: внешняя, которая Expression, потому что передается в Where, который вызывается у DbSet, который реализует IQueryable. И внутренняя, которая Func , потому что вызывается у ICollection, она не Expression, потому что ICollection не реализует IQueryable.
Т.к. первая лямбда - Expression, то query provider (или что там парсит запрос) сможет его распарсить и получить инфу обо всех "узлах", которые заюзаны в выражении: о полях объекта(o.Name, o.Users) , о переменных ("Roga"), об операторах (в данном случае &&) и о методах, которые вызываются на полях(методы Any и Contains, и дальше парсер, зная, что вызываются Any и Contains, уже поймет, в какие sql-команды преобразовать эти методы). Это как я понимаю происходящее при использовании Expression.
Но тут меня напрягает то, что мы в метод Any передаем вложенную лямбду, которая не является экспрешшеном. Ведь парсер должен получить изнутри вложенной лямбды инфу о том, что "у Юзера нам нужно взять поле FirstName и сравнить его с "Petr"). Но вложенная лямбда не является экспрешшеном, т.е., распарсить ее не получится? Или несмотря на то, что вложенная лямбда - не Expression, а Func, но за счет того, что она внутри экспрешшена, сишарп все-равно ее передаст как Expression и она сможет быть распарсена?
Я провел тест, который показал, что запрос в БД содержал фильтрацию по юзернейму "Petr", вот такой был запрос:
FROM  [dbo].[Organizations] AS [Extent1]
    WHERE ([Extent1].[Name] LIKE N'%Roga%') AND ( EXISTS (SELECT 
        1 AS [C1]
        FROM [dbo].[AspNetUsers] AS [Extent3]
        WHERE ([Extent1].[Id] = [Extent3].[OrganizationId]) AND (N'Petr' = [Extent3].[FirstName])
    ))

что говорит о том, что лямбда-Func была распарсена движком подобно лямбде-экспрешшену. За счет чего это произошло?
Я, вообще, раньше считал, что тут вложенная лямбда распарсится (каким бы то ни было образом), что, собсвенно, и произошло. Но вот я только что захотел вынести вложенную функцию в отдельную переменную, чтобы заюзать ее дальше в Select. Т.е., я хотел сделать так:
Func<User, bool> func = u => u.FirstName == "Petr";
            return _dbContext.Organizations.Where(o => o.Name.Contains("Roga") && o.Users.Any(func)).Select(...)

и тут я подумал, если я так сделаю, то это уже будет конкретный вызов сишарповой переменной func изнутри экспрешшена, а это явно должно привести к падению. Я проверил этот код, и он реально кинул эксепшен
Internal .NET Framework Data Provider error 1025.


В общем, мои вопросы:
1)любая вложенная в Expression лямбда тоже является экспрешшеном, несмотря на то, что ее тип может быть показан студией, как Func?
2)если да, поэтому мы не можем ее вынести в отдельную переменную, чтобы заюзать в нескольких местах, потому что тогда мы во внешнем экспрешшене используем не лямбду, а ссылку на переменную с лямбдой?
3)как-нибудь можно решить проблему невозможности вынесения вложенной лямбды в отдельную переменную? Объявить переменную как Expression не получается, т.к. Any на ICollection не принимает Expression.
  • Вопрос задан
  • 353 просмотра
Пригласить эксперта
Ответы на вопрос 2
@footballer Автор вопроса
Как я понял, данная проблема невозможности выноса вложенной лямбды в отдельную переменную вызвана тупостью сишарпового компилятора? Т.к. он мог бы во время компиляции распознать, что мы в Any передаем переменную типа Func, и вместо экспрешшена со ссылкой на переменную сгенерить экспрешшен с вложенной лямблой, которую бы он прочитал из переменной во время компиляции? Но он вместо этого создает экспрешшен со ссылкой на переменную, из-за чего парсер падает?
Ответ написан
Я не уверен, но моя версия следующая. В первом случаи нету лямбды, там сплошной експрешен следующего вида:

o => o.Name.Contains("Roga") && o.Users.Any(u => u.FirstName == "Petr")


т.е. (u => u.FirstName == "Petr") это екпрешин вложеный в експрешин.
Во втором случаи там настоящая лябда, которая передается из вне, вот оно и ругается.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы