Я неправильно понимаю 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 означает, что данные не выбираются, пока они не будут вызваны кодом, выполняющим вычисления. Я предполагаю, что кроется в том, что к базе данных обращаются три раза - по одному на каждый расчет, и данные не сохраняются в памяти.

Другие вопросы по тегам