Ответ
IQueryable<T> не выполняет лямбда-выражения напрямую. Вместо этого он компилирует их в дерево выражений (Expression Tree), которое представляет собой структуру данных, описывающую логику запроса (фильтрацию, сортировку, проекцию). Это дерево затем передаётся поставщику запросов (query provider), например, Entity Framework Core, который анализирует его и транслирует в специфичный для базы данных SQL.
Пример и сравнение с IEnumerable:
// IQueryable: Трансляция в SQL
IQueryable<User> query = dbContext.Users
.Where(u => u.Age > 18 && u.IsActive) // => WHERE Age > 18 AND IsActive = 1
.OrderBy(u => u.LastName) // => ORDER BY LastName
.Select(u => new { u.Id, u.Name }); // => SELECT Id, Name FROM Users
// Запрос выполнится в БД только здесь:
var result = query.ToList();
// IEnumerable: Выполнение в памяти
IEnumerable<User> inMemoryQuery = dbContext.Users.AsEnumerable()
.Where(u => u.Age > 18 && u.IsActive); // ВСЕ строки загружаются в память, фильтр применяется в C#
Ключевые механизмы:
- Expression Trees: Лямбда
u => u.Age > 18компилируется не в IL-код, а в объект типаExpression<Func<User, bool>>. Это позволяет анализировать её структуру (параметрu, свойствоAge, оператор>, константа18) во время выполнения. - Поставщик запросов (IQueryProvider): Отвечает за создание и выполнение запроса. EF Core имеет своего провайдера для каждой СУБД (SQL Server, PostgreSQL и т.д.), который знает, как преобразовать операции
Where,Select,Joinв соответствующие SQL-конструкции. - Отложенное выполнение (Deferred Execution): Запрос не выполняется до момента материализации результатов (
ToList(),ToArray(),FirstOrDefault(),Count()).
Важные ограничения:
- Не вся логика C# может быть переведена в SQL. Например, вызов произвольных методов
.Where(u => MyCustomMethod(u.Age))вызовет ошибку на этапе трансляции. - Для сложной логики, непереводимой в SQL, можно сначала отфильтровать данные на стороне БД с помощью
IQueryable, а затем доработать в памяти с помощьюIEnumerable(AsEnumerable()).