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 к 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
два раза, вы получите два списка с совершенно разными объектами. Они могут содержать одни и те же данные, но они будут разными ссылками.
Edit после комментариев
Я просто хочу, чтобы иметь четкое представление о различии между тем, когда все делается на стороне клиента, и когда они сделаны на стороне сервера. Если вы ссылаетесь на 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"
- то есть может отфильтрованы на сервере.
@Adam Robinson - так лучше, чтобы они могли получить возвращенный запрос, который все еще не попал в базу данных, и когда ему нужен список, он может перебирать его (а затем он попадает в источник db).Или лучше просто дать им список с места в карьер? – chobo2
@ chobo2: Это зависит от того, что вы пытаетесь сделать, поэтому нет ни одного правильного ответа. В некоторых случаях может быть полезно разрешить пользователю дополнительно уточнять запрос, особенно в тех случаях, когда у вас может быть сложная логика запросов, которая является общей для нескольких разных запросов. Хотя это не говорит о том, что вы всегда должны * делать это, возвращая 'IQueryable' с базовой комплексной логикой на месте, вы сможете повторно использовать эту логику в разных местах, не дублируя ее в коде –
@Adam Robinson -что бы произошло в этот сценарий. Я возвращаю список IQueryable, но мне нужно использовать IEnumerator. Я бы бросил его на это, будет ли какая-то проблема, о которой вы говорили, с Enumerable? – chobo2