https://habrahabr.ru/post/108407/ - статья.
И мои мысли с вопросами по ней:
Насколько я понимаю LINQ-запросы, выполнение Select, Where и др. на IEnumerable должно привести к тому, что запрос не будет преобразован в sql, и условие выполнится после получения всех данных из БД, потому что перегрузки этих методов принимают не Expression , а Func. Наоборот, реализации Select, Where и др. на IQueryable принимают Expression, который будет преобразован в sql , поэтому условия запроса будут выполнены на сервере БД.
Автор в своем коде внутри метода ToModelUsers вызывает Select на IEnumerable, поэтому у него происходит несколько запросов в БД.
Далее, автор пишет:
Пытаюсь излечить данную проблему, меняю код репозитория у на следующий:
где он просто внутрь метода GetUsers добавил такой код:
.Select(u => new Model.User {
Id = u.Id, Name = u.Name, Parameters = u.Parameters.Select(p => new Model.Parameter {… })
});
и сразу у автора:
Картина меняется кардинально. Всего 1 запрос, в котором грузятся как user-ы так и Parameter-ы
Тут меня насторожило, как же так, ведь код метода ToModelUsers не изменился и по-прежнему выполняет Select на IEnumerable. Я решил проверить это в линкпаде. Написал аналогичный код, юзающий контект EF (только вместо методов-расширений, которые использовал автор, я заюзал обычные методы, принимающие переменную, т.к. линкпад по непонятной причине не мог найти методы-расширения, но это мое изменение не должно было ни на что влиять). Мой код, который был написан таким же, как первоначальный код автора, действительно вызвал кучу запросов в БД (не было джойна). Потом я изменил код, добавив то же, что добавил автор, но, как я и предположил, кол-во запросов не изменилось.
1) Поэтому мой первый вопрос: почему автор пишет, что добавление Select внутрь метода GetUsers привело к тому, что вместо кучи запросов теперь выполняется только 1 запрос, если мой эксперимент это опровергает? Может, автор что-то напутал?
Дальше автор пишет:
Есть 2 пути, либо указывать тип IQueryable для возвращаемого значения
и меняет тип возвращаемого значения у метода ToModelUsers с IEnumerable на IQueryable. Я продолжил свой эксперимент, и выяснилось, что чтобы получить только 1 запрос, не обязательно менять возращаемый тип у метода ToModelUsers, как сделал автор, а достаточно поменять тип принимаемого параметра с IEnumerable на IQueryable только у метода ToModelUsers. После этого внутри метода ToModelUsers мы выполняем Select не на IEnumerable, а на IQueryable, поэтому передаем в Select Expression, а не Func, за счет чего запрос трансформируется в sql. То есть, эксперимент показал, не важно, выполнять ли ToList на IEnumerable или на IQueryable, т.е. не всегда нужно все приводить к IQueryable. Важно, вызовем ли мы Select на IQueryable вместо IEnumerable, передавая туда именно Expression, а не Func. Так что, как мне кажется, автор и тут что-то напутал.
Дальше, тут уже я сам не понял происходящего. Метод ToModelParameters вызывается изнутри Expression из метода ToModelUsers, т.е. при попытке конвертировать экспрешшен в sql должен упасть эксепшен (как я помню, всегда так и было). Но при выполнении кода в линкпаде никаких эксепшенов не падает, все нормально выполняется в 1 запрос. Я решил проверить в студии в окне Interactive, ввел туда аналогичный код, запустил, и в студии как раз упал этот эксепшен:
LINQ to Entities does not recognize the method 'System.Collections.Generic.IEnumerable`1[Model.Parameter] ToModelParameters(System.Collections.Generic.IEnumerable`1[Model.Parameter])' method, and this method cannot be translated into a store expression.
2) Так что мой следующий вопрос: как такое может быть, что вызов метода ToModelParameters изнутри экспрешшена работает в линкпаде?