Вы должны знать о двух различиях:
- Разница между IEnumerable и IQueryable
- Разница между функциями, которые возвращают
IQueryable<TResult>
(ленивый), и функциями, которые возвращают TResult
(выполнение)
Разница между Enumerable
и Queryable
. Оператор LINQ AsEnumerable
предназначен для обработки в локальном процессе. Он содержит весь код и все вызовы для выполнения оператора. Этот оператор выполняется, как только вызываются GetEnumerator
и MoveNext
, явно или неявно, с использованием операторов foreach
или LINQ, которые не возвращают IEnumerable<...>
, например ToList
, FirstOrDefault
и Any
.
Напротив, IQueryable
не предназначен для обработки в вашем процессе (однако это может быть сделано, если вы хотите). Обычно он обрабатывается другим процессом, обычно системой управления базами данных.
Для этого IQueryable
содержит Expression
и Provider
. Expression
представляет запрос, который должен быть выполнен. Provider
знает, кто должен выполнить запрос (СУБД) и на каком языке этот исполнитель использует (обычно SQL). Когда вызываются GetEnumerator
и MoveNext
, Provider
берет Expression
и переводит его на язык Executor
. Запрос не отправлен исполнителю. Возвращенные данные представлены AsEnumerable
, где вызываются GetEnumerator
и MoveNext
.
Из-за этого перевода в SQL IQueryable не может делать все то, что может делать IEnumerable. Главное, что он не может вызывать ваши локальные функции. Он не может даже выполнить все функции LINQ. Чем лучше качество Provider
, тем больше он может сделать. См. поддерживаемые и неподдерживаемые методы LINQ
Ленивые методы LINQ и выполнение методов LINQ
Существует две группы методов LINQ. Те, которые возвращают `IQueryable <...> / IEnumerable <...> и те, которые не возвращают.
Первая группа использует ленивую загрузку. Это означает, что в конце оператора LINQ запрос был создан, но он еще не выполнен. Только 'GetEnumerator and
MoveNext will make that the
Provider will translate the
Expression` и приказать СУБД выполнить запрос.
Объединение IQueryables
изменит только Expression
. Это довольно быстрая процедура. Следовательно, производительность не улучшится, если вы создадите одно большое выражение LINQ вместо того, чтобы объединить их перед выполнением запроса.
Обычно СУБД умнее и лучше подготовлена для выбора, чем ваш процесс. Перенос выбранных данных в локальный процесс является одной из медленных частей вашего запроса.
Совет: попробуйте создать ваши операторы LINQ такими, чтобы выполняющиеся
оператор является последним, который может быть выполнен СУБД. Удостовериться
что вы выбираете только те свойства, которые фактически планируете использовать.
Так, например, не передавайте внешние ключи, если вы ими не пользуетесь.
Вернуться к вашему вопросу
Оставляя маппер вне вопроса, с которого вы начинаете:
db.Users.SingleOrDefault(...)
SingleOrDefault - не ленивая функция. Не возвращается IQueryable<...>
. Он выполнит запрос. Он доставит один полный User
в ваш локальный процесс, включая Roles
.
Совет отложить SingleOrDefault до последнего оператора:
var result = myDbcontext.Users
.Where(user => user.InternetId == username)
.SelectMany(user => user.Groups.Roles.Where(role => role.Asset.AssetName == application))
// until here, the query is not executed yet, execute it now:
.SingleOrDefault();
В словах: из последовательности Users
оставьте только те Users
с InternetId
, равным userName
. Из всех оставшихся Users
(которые, как вы надеетесь, будет только один), выберите последовательность Roles
из Groups
каждого User
. Однако мы не хотим выбирать все Roles
, мы сохраняем только Roles
с AssetName
равным application
. Теперь поместите все оставшиеся Roles
в одну коллекцию (часть many
в SelectMany
) и выберите ноль или один оставшийся Role
, который вы ожидаете.