IQueryable<T>
предназначен для того, чтобы поставщик запросов (например, ORM, такой как LINQ to SQL или Entity Framework) мог использовать выражения, содержащиеся в запросе, для перевода запроса в другой формат. Другими словами, LINQ-to-SQL просматривает свойства сущностей, которые вы используете, а также выполняемые вами сравнения и фактически создает инструкцию SQL для выражения (надеюсь) эквивалентного запроса.
IEnumerable<T>
является более общим, чем IQueryable<T>
(хотя все экземпляры IQueryable<T>
реализуют IEnumerable<T>
) и определяет только последовательность. Однако в классе Enumerable
доступны методы расширения, которые определяют некоторые операторы типа запроса в этом интерфейсе и используют обычный код для оценки этих условий.
List<T>
- это просто выходной формат, и хотя он реализует IEnumerable<T>
, он не имеет прямого отношения к запросам.
Другими словами, когда вы используете IQueryable<T>
, вы определяете выражение , которое переводится во что-то другое. Даже если вы пишете код, этот код никогда не будет выполнен , он будет только проверен и превращен во что-то другое, как настоящий SQL-запрос. Из-за этого только определенные вещи допустимы в этих выражениях. Например, вы не можете вызывать обычную функцию, которую вы определяете из этих выражений, поскольку LINQ-to-SQL не знает, как превратить ваш вызов в оператор SQL. К сожалению, большинство из этих ограничений оцениваются только во время выполнения.
Когда вы используете IEnumerable<T>
для запросов, вы используете LINQ-to-Objects, что означает, что вы пишете фактический код, который используется для оценки вашего запроса или преобразования результатов, так что, как правило, нет ограничения на то, что вы можете сделать. Вы можете свободно вызывать другие функции из этих выражений.
С LINQ to SQL
Идя рука об руку с вышеупомянутым различием, также важно помнить, как это работает на практике. Когда вы пишете запрос к классу контекста данных в LINQ to SQL, он выдает IQueryable<T>
. Что бы вы ни делали против самого IQueryable<T>
, превратится в SQL, поэтому ваша фильтрация и преобразование будут выполняться на сервере. Все, что вы делаете против этого как IEnumerable<T>
, будет сделано на уровне приложения. Иногда это желательно (например, когда вам нужно использовать код на стороне клиента), но во многих случаях это непреднамеренно.
Например, если у меня был контекст со свойством Customers
, представляющим таблицу Customer
, и у каждого клиента есть столбец CustomerId
, давайте рассмотрим два способа сделать этот запрос:
var query = (from c in db.Customers where c.CustomerId == 5 select c).First();
Это создаст SQL, который запрашивает базу данных для записи Customer
с CustomerId
, равным 5. Что-то вроде:
select CustomerId, FirstName, LastName from Customer where CustomerId = 5
Что произойдет, если мы превратим Customers
в IEnumerable<Customer>
с помощью метода расширения AsEnumerable()
?
var query = (from c in db.Customers.AsEnumerable() where c.CustomerId == 5 select c).First();
Это простое изменение имеет серьезные последствия. Так как мы превращаем Customers
в IEnumerable<Customer>
, это вернет всю таблицу обратно и отфильтрует ее на стороне клиента (строго говоря, это вернет каждую строку в таблице , пока она не встретит ту, которая соответствует критерию , но смысл тот же).
ToList ()
До сих пор мы говорили только о IQueryable
и IEnumerable
. Это потому, что они похожи, бесплатные интерфейсы. В обоих случаях вы определяете запрос ; то есть вы определяете , где , чтобы найти данные, , какие фильтры для применения и , какие данные для возврата. Оба эти запроса
query = from c in db.Customers where c.CustomerId == 5 select c;
query = from c in db.Customers.AsEnumerable() where c.CustomerId == 5 select c;
Как мы уже говорили, первый запрос использует IQueryable
, а второй - IEnumerable
. Однако в обоих случаях это всего лишь запрос . Определение запроса фактически ничего не делает с источником данных. Запрос фактически выполняется, когда код начинает перебирать список. Это может произойти несколькими способами; foreach
цикл, вызов ToList()
и т. д.
Запрос выполняется в первые и каждый каждый раз, когда он повторяется.Если бы вы дважды вызывали ToList()
на query
, вы бы получили два списка с совершенно разными объектами.Они могут содержать одни и те же данные, но они будут разными ссылками.
Редактировать после комментариев
Я просто хочу прояснить различие между тем, когда все делается клиентоми когда они сделаны на стороне сервера.Если вы ссылаетесь на IQueryable<T>
как IEnumerable<T>
, только , запрос сделан после , это IEnumerable<T>
будет выполняться на стороне клиента.Например, скажем, у меня есть эта таблица и контекст LINQ-to-SQL:
Customer
-----------
CustomerId
FirstName
LastName
Сначала я строю запрос на основе FirstName
.Это создает IQueryable<Customer>
:
var query = from c in db.Customers where c.FirstName.StartsWith("Ad") select c;
Теперь я передаю этот запрос функции, которая принимает IEnumerable<Customer>
и выполняет некоторую фильтрацию на основе LastName
:
public void DoStuff(IEnumerable<Customer> customers)
{
foreach(var cust in from c in customers where c.LastName.StartsWith("Ro"))
{
Console.WriteLine(cust.CustomerId);
}
}
Мыздесь сделали второй запрос, но он выполняется на IEnumerable<Customer>
.Здесь произойдет то, что первый запрос будет оценен с использованием этого SQL:
select CustomerId, FirstName, LastName from Customer where FirstName like 'Ad%'
Итак, мы собираемся вернуть всех, кто FirstName
начинается с "Ad"
.Обратите внимание, что здесь ничего нет о LastName
.Это потому, что он фильтруется на стороне клиента.
Как только он возвращает эти результаты, программа затем перебирает результаты и доставляет только те записи, чья LastName
начинается с "Ro"
.Недостатком этого является то, что мы вернули данные, а именно, все строки, чьи LastName
не начинаются с "Ro"
- что может отфильтроваться насервер.