Я неправильно понимаю LINQ to SQL .AsEnumerable()?
Рассмотрим этот код:
var query = db.Table
.Where(t => SomeCondition(t))
.AsEnumerable();
int recordCount = query.Count();
int totalSomeNumber = query.Sum();
decimal average = query.Average();
Предполагать query
очень долго бежит Мне нужно получить количество записей, всего SomeNumber
вернулся, и взять в среднем в конце. Я думал, основываясь на моем чтении, что .AsEnumerable()
будет выполнять запрос с использованием LINQ-to-SQL, а затем использовать LINQ-to-Objects для Count
, Sum
, а также Average
, Вместо этого, когда я делаю это в LINQPad, я вижу, что один и тот же запрос выполняется три раза. Если я заменю .AsEnumerable()
с .ToList()
, он запрашивается только один раз.
Я что-то упускаю из-за чего? AsEnumerable
это / делает?
6 ответов
Призвание AsEnumerable(
) не выполняет запрос, перечисляя его.
IQueryable
это интерфейс, который позволяет LINQ to SQL
выполнять свою магию. IQueryable
инвентарь IEnumerable
поэтому, когда вы звоните AsEnumerable()
, вы меняете методы расширения, вызываемые с этого момента, то есть из IQueryable
-методы к IEnumerable
-методы (т.е. изменение от LINQ to SQL
в LINQ to Objects
в данном конкретном случае). Но вы не выполняете реальный запрос, а просто меняете способ его выполнения.
Для принудительного выполнения запроса вы должны вызвать ToList()
,
Да. Все это AsEnumerable
будет сделать, это вызвать Count
, Sum
, а также Average
функции, выполняемые на стороне клиента (другими словами, он вернет весь набор результатов клиенту, затем клиент выполнит эти агрегаты вместо создания COUNT()
SUM()
а также AVG()
операторы в SQL).
Ответ Джастина Нисснера идеален.
Я просто хочу процитировать MSDN здесь: .NET-встроенный запрос для реляционных данных
Оператор AsEnumerable (), в отличие от ToList () и ToArray (), не вызывает выполнения запроса. Это все еще отложено. Оператор AsEnumerable () просто изменяет статическую типизацию запроса, превращая IQueryable в IEnumerable, обманывая компилятор, чтобы остальная часть запроса воспринималась как выполненная локально.
Я надеюсь, что это то, что подразумевается под:
IQueryable-методы для IEnumerable-методов (т. Е. Переход с LINQ на SQL на LINQ на объекты
Как только это LINQ to Objects, мы можем применять методы объекта (например, ToString ()). Это объяснение одного из часто задаваемых вопросов о LINQ. Почему LINQ to Entities не распознает метод System.String ToString()?
Согласно ASENUMERABLE - codeblog.jonskeet, AsEnumerable
может быть удобно, когда:
некоторые аспекты запроса в базе данных, а затем немного больше манипуляций в.NET, особенно если есть аспекты, которые вы в принципе не можете реализовать в LINQ to SQL (или любом другом поставщике, который вы используете).
Это также говорит:
Все, что мы делаем, - это изменяем тип времени компиляции последовательности, которая распространяется через наш запрос, с IQueryable на IEnumerable, но это означает, что компилятор будет использовать методы в Enumerable (принимая делегаты и выполняя в LINQ to Objects) вместо этого из тех, что в Queryable (взяв деревья выражений, и обычно выполняя вне процесса).
Наконец, также посмотрите этот связанный вопрос: Возвращение IEnumerable против IQueryable
Ну, вы на правильном пути. Проблема в том, что IQueryable
(что это за заявление до AsEnumerable
вызов) также IEnumerable
, так что этот вызов, по сути, не nop. Это потребует принудительного использования определенной структуры данных в памяти (например, ToList()
) форсировать запрос.
Просто добавлю еще немного пояснений:
Основываясь на моем чтении, я подумал, что запрос будет выполняться с использованием LINQ-to-SQL.
Как объясняет ответ Джастина , он не выполнит запрос сразу . Только материализуется (попадет в базу данных) позже.
Вместо этого, когда я делаю это в LINQPad, я вижу, что один и тот же запрос выполняется три раза.
Да, и обратите внимание, что все три запроса абсолютно одинаковы , в основном извлекая все строки из заданного условия в память, а затем локально вычисляя количество/сумму/среднее.
Если я заменю на
.ToList()
, он запрашивается только один раз.
Но по-прежнему получать все данные в память, с тем преимуществом, что теперь он запускается только один раз.
Если повышение производительности вызывает беспокойство, просто удалите.AsEnumerable()
и тогда count/sum/avg будут корректно переведены в их SQL-корреспонденты. При этом будут выполняться три запроса (вероятно, быстрее, если есть индекс, удовлетворяющий условиям), но с гораздо меньшим объемом памяти.
Я бы предположил, что ToList заставляет Linq извлекать записи из базы данных. Когда вы затем выполняете текущие вычисления, они выполняются с объектами в памяти, а не с базой данных.
Оставление возвращаемого типа в качестве Enumerable означает, что данные не выбираются, пока они не будут вызваны кодом, выполняющим вычисления. Я предполагаю, что кроется в том, что к базе данных обращаются три раза - по одному на каждый расчет, и данные не сохраняются в памяти.