В чем разница между IEnumerable и IQueryable в C#?
Что проверяют
Понимаете ли вы, что один фильтрует в памяти, а другой транслируется в SQL, и знаете ли цену ошибки.
Как отвечать
IEnumerable<T> это последовательность, которую перебирают в памяти приложения. Методы LINQ принимают делегаты (Func<T, bool>), то есть обычный скомпилированный код. Фильтрация происходит после того, как данные уже получены.
IQueryable<T> хранит не код, а дерево выражений (Expression<Func<T, bool>>). Провайдер, например EF Core, разбирает это дерево и переводит его в SQL, поэтому фильтрация, сортировка и выборка колонок выполняются на стороне базы.
Ошибка, ради которой вопрос и задают:
IEnumerable<User> users = db.Users; // тип потерян
var active = users.Where(u => u.IsActive); // вся таблица уже в памятиЗдесь Where уже не попадёт в SQL: EF выполнит SELECT * FROM Users, вытянет все строки и отфильтрует их в приложении. На таблице в миллион строк это разница между миллисекундами и падением по памяти. То же самое делает явный вызов AsEnumerable() или ToList() в середине цепочки.
Отсюда правило: репозиторий не должен возвращать IEnumerable<T> там, где предполагается дальнейшая фильтрация. Либо отдавайте IQueryable<T> и стройте запрос дальше, либо принимайте условия параметрами и отдавайте уже готовый результат.
Обе конструкции ленивы: запрос не выполняется до перебора, ToList, First, Count. Но лень у них разная по месту исполнения.
Ограничение IQueryable: транслируется не всё. Вызов собственного метода C# внутри Where провайдер разобрать не сможет, и EF Core бросит исключение о непереводимом выражении. Раньше он молча переключался на вычисление в памяти, что и было источником неожиданных полных выборок.
Где спотыкаются
Возвращают IEnumerable из слоя доступа к данным.
Ставят ToList() в середине цепочки «чтобы посмотреть».
Не проверяют сгенерированный SQL.
Считают, что раз выполнение отложено, то всё выполнится на сервере.