Какая польза IQueryable
в контексте LINQ?
Используется ли он для разработки методов расширения или для каких-либо других целей?
Какая польза IQueryable
в контексте LINQ?
Используется ли он для разработки методов расширения или для каких-либо других целей?
Ответы:
Ответ Марка Гравелла очень полный, но я решил добавить кое-что об этом и с точки зрения пользователя ...
Основное отличие, с точки зрения пользователя, заключается в том, что при использовании IQueryable<T>
(с поставщиком, который поддерживает все правильно) вы можете сэкономить много ресурсов.
Например, если вы работаете с удаленной базой данных во многих системах ORM, у вас есть возможность извлекать данные из таблицы двумя способами: один возвращает IEnumerable<T>
, а другой возвращает IQueryable<T>
. Например, у вас есть таблица «Продукты», и вы хотите получить все продукты, стоимость которых> 25 долларов США.
Если вы делаете:
IEnumerable<Product> products = myORM.GetProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
Здесь происходит то, что база данных загружает все продукты и передает их по проводам в вашу программу. Ваша программа затем фильтрует данные. По сути, база данных делает SELECT * FROM Products
и возвращает вам КАЖДЫЙ продукт.
С подходящим IQueryable<T>
поставщиком, с другой стороны, вы можете сделать:
IQueryable<Product> products = myORM.GetQueryableProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
Код выглядит так же, но разница здесь в том, что выполненный SQL будет SELECT * FROM Products WHERE Cost >= 25
.
С вашей точки зрения, как разработчика, это выглядит так же. Однако с точки зрения производительности вы можете вернуть только 2 записи по сети вместо 20 000 ...
IQueryable<Product>
- будет специфичным для вашего ORM или репозитория и т. Д.
foreach
или вызовите ToList()
), вы фактически не попадете в БД.
По сути, его работа очень похожа на IEnumerable<T>
представление запрашиваемого источника данных. Разница заключается в том, что различные методы LINQ (on Queryable
) могут быть более конкретными: создавать запрос с использованием Expression
деревьев, а не делегатов (что и Enumerable
используется).
Деревья выражений могут быть проверены выбранным вами поставщиком LINQ и превращены в реальный запрос - хотя это само по себе чёрное искусство.
Это действительно до ElementType
, Expression
и Provider
- но на самом деле вам редко нужно заботиться об этом как о пользователе . Только исполнитель LINQ должен знать мрачные детали.
Повторные комментарии; Я не совсем уверен, что вы хотите в качестве примера, но рассмотрим LINQ-to-SQL; Центральный объект здесь представляет собой DataContext
, который представляет нашу обертку базы данных. Это обычно имеет свойство для таблицы (например, Customers
), и таблица реализует IQueryable<Customer>
. Но мы не используем так много напрямую; рассматривать:
using(var ctx = new MyDataContext()) {
var qry = from cust in ctx.Customers
where cust.Region == "North"
select new { cust.Id, cust.Name };
foreach(var row in qry) {
Console.WriteLine("{0}: {1}", row.Id, row.Name);
}
}
это делается (компилятором C #):
var qry = ctx.Customers.Where(cust => cust.Region == "North")
.Select(cust => new { cust.Id, cust.Name });
который снова интерпретируется (компилятором C #) как:
var qry = Queryable.Select(
Queryable.Where(
ctx.Customers,
cust => cust.Region == "North"),
cust => new { cust.Id, cust.Name });
Важно отметить, что статические методы Queryable
принимают деревья выражений, которые, вместо обычного IL, компилируются в объектную модель. Например - просто глядя на «Где», это дает нам нечто сравнимое с:
var cust = Expression.Parameter(typeof(Customer), "cust");
var lambda = Expression.Lambda<Func<Customer,bool>>(
Expression.Equal(
Expression.Property(cust, "Region"),
Expression.Constant("North")
), cust);
... Queryable.Where(ctx.Customers, lambda) ...
Разве компилятор не сделал для нас много? Эту объектную модель можно разорвать на части, проверить, что это значит, и снова собрать вместе с помощью генератора TSQL - что-то вроде:
SELECT c.Id, c.Name
FROM [dbo].[Customer] c
WHERE c.Region = 'North'
(строка может оказаться в качестве параметра; я не могу вспомнить)
Ничего из этого не было бы возможным, если бы мы только что использовали делегата. И это точка Queryable
/ IQueryable<T>
: она обеспечивает точку входа для использования деревьев выражений.
Все это очень сложно, поэтому хорошо, что компилятор делает это легко и просто для нас.
Для получения дополнительной информации посмотрите « C # in Depth » или « LINQ в действии », которые оба охватывают эти темы.
Хотя Рид Копси и Марк Гравелл уже достаточно подробно описали IQueryable
(а также IEnumerable
), я хочу добавить немного больше, приведя небольшой пример, IQueryable
и IEnumerable
столько пользователей просили об этом.
Пример : я создал две таблицы в базе данных
CREATE TABLE [dbo].[Employee]([PersonId] [int] NOT NULL PRIMARY KEY,[Gender] [nchar](1) NOT NULL)
CREATE TABLE [dbo].[Person]([PersonId] [int] NOT NULL PRIMARY KEY,[FirstName] [nvarchar](50) NOT NULL,[LastName] [nvarchar](50) NOT NULL)
Первичный ключ ( PersonId
) таблицы Employee
также является поддельным ключом ( personid
) таблицыPerson
Затем я добавил модель сущности ado.net в свое приложение и создал ниже класс обслуживания на этом
public class SomeServiceClass
{
public IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable(IEnumerable<int> employeesToCollect)
{
DemoIQueryableEntities db = new DemoIQueryableEntities();
var allDetails = from Employee e in db.Employees
join Person p in db.People on e.PersonId equals p.PersonId
where employeesToCollect.Contains(e.PersonId)
select e;
return allDetails;
}
public IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable(IEnumerable<int> employeesToCollect)
{
DemoIQueryableEntities db = new DemoIQueryableEntities();
var allDetails = from Employee e in db.Employees
join Person p in db.People on e.PersonId equals p.PersonId
where employeesToCollect.Contains(e.PersonId)
select e;
return allDetails;
}
}
они содержат одно и то же linq. Это называется program.cs
как определено ниже
class Program
{
static void Main(string[] args)
{
SomeServiceClass s= new SomeServiceClass();
var employeesToCollect= new []{0,1,2,3};
//IQueryable execution part
var IQueryableList = s.GetEmployeeAndPersonDetailIQueryable(employeesToCollect).Where(i => i.Gender=="M");
foreach (var emp in IQueryableList)
{
System.Console.WriteLine("ID:{0}, EName:{1},Gender:{2}", emp.PersonId, emp.Person.FirstName, emp.Gender);
}
System.Console.WriteLine("IQueryable contain {0} row in result set", IQueryableList.Count());
//IEnumerable execution part
var IEnumerableList = s.GetEmployeeAndPersonDetailIEnumerable(employeesToCollect).Where(i => i.Gender == "M");
foreach (var emp in IEnumerableList)
{
System.Console.WriteLine("ID:{0}, EName:{1},Gender:{2}", emp.PersonId, emp.Person.FirstName, emp.Gender);
}
System.Console.WriteLine("IEnumerable contain {0} row in result set", IEnumerableList.Count());
Console.ReadKey();
}
}
Вывод одинаков для обоих, очевидно,
ID:1, EName:Ken,Gender:M
ID:3, EName:Roberto,Gender:M
IQueryable contain 2 row in result set
ID:1, EName:Ken,Gender:M
ID:3, EName:Roberto,Gender:M
IEnumerable contain 2 row in result set
Итак, вопрос в том, в чем / где разница? Кажется, нет никакой разницы, верно? В самом деле!!
Давайте посмотрим на sql запросы, сгенерированные и выполненные примитивами framwork 5 за этот период.
IQueryable исполнительная часть
--IQueryableQuery1
SELECT
[Extent1].[PersonId] AS [PersonId],
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE ([Extent1].[PersonId] IN (0,1,2,3)) AND (N'M' = [Extent1].[Gender])
--IQueryableQuery2
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM [dbo].[Employee] AS [Extent1]
WHERE ([Extent1].[PersonId] IN (0,1,2,3)) AND (N'M' = [Extent1].[Gender])
) AS [GroupBy1]
IEnumerable часть исполнения
--IEnumerableQuery1
SELECT
[Extent1].[PersonId] AS [PersonId],
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE [Extent1].[PersonId] IN (0,1,2,3)
--IEnumerableQuery2
SELECT
[Extent1].[PersonId] AS [PersonId],
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE [Extent1].[PersonId] IN (0,1,2,3)
Общий сценарий для обеих исполнительных частей
/* these two query will execute for both IQueryable or IEnumerable to get details from Person table
Ignore these two queries here because it has nothing to do with IQueryable vs IEnumerable
--ICommonQuery1
exec sp_executesql N'SELECT
[Extent1].[PersonId] AS [PersonId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName]
FROM [dbo].[Person] AS [Extent1]
WHERE [Extent1].[PersonId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1
--ICommonQuery2
exec sp_executesql N'SELECT
[Extent1].[PersonId] AS [PersonId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName]
FROM [dbo].[Person] AS [Extent1]
WHERE [Extent1].[PersonId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=3
*/
Итак, у вас есть несколько вопросов, позвольте мне угадать их и попытаться ответить на них.
Почему разные сценарии генерируются для одного и того же результата?
Давайте выясним некоторые моменты здесь,
все запросы имеют одну общую часть
WHERE [Extent1].[PersonId] IN (0,1,2,3)
Зачем? Потому что как функция, так IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable
и
IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable
of SomeServiceClass
содержит одну общую строку в запросах linq
where employeesToCollect.Contains(e.PersonId)
Почему
AND (N'M' = [Extent1].[Gender])
часть отсутствует в IEnumerable
части исполнения, а при вызове обеих функций мы использовали Where(i => i.Gender == "M") in
program.cs`
Сейчас мы находимся в точке , где разница разлучила
IQueryable
иIEnumerable
Что делает фреймворк для сущностей при IQueryable
вызове метода, он обрабатывает оператор linq, написанный внутри метода, и пытается выяснить, определено ли больше выражений linq в результирующем наборе, затем он собирает все определенные запросы linq до тех пор, пока результат не должен быть извлечен, и не создает более подходящий sql запрос для выполнения.
Это обеспечивает много преимуществ, таких как,
как здесь, в примере, сервер sql вернул приложению только две строки после выполнения IQueryable`, но возвратил ТРИ строки для запроса IEnumerable, почему?
В случае IEnumerable
метода, структура сущности взяла оператор linq, написанный внутри метода, и создает запрос sql, когда нужно получить результат. он не включает в себя rest linq part для создания запроса sql. Как и здесь, в столбце sql server не выполняется фильтрация gender
.
Но выходы такие же? Потому что 'IEnumerable фильтрует результат дальше на уровне приложения после получения результата от сервера SQL
Итак, что выбрать? Лично я предпочитаю определять результат функции, IQueryable<T>
потому что есть много преимуществ, которые он имеет, IEnumerable
например, вы можете объединить две или более функций IQueryable, которые генерируют более специфический скрипт для сервера sql.
Здесь в примере вы можете увидеть, что IQueryable Query(IQueryableQuery2)
генерирует более конкретный сценарий, чем тот, IEnumerable query(IEnumerableQuery2)
который на мой взгляд более приемлем.
Это позволяет для дальнейшего запроса дальше вниз по линии. Если бы это было, скажем, за пределами сервисной границы, то пользователю этого объекта IQueryable было бы разрешено делать с ним больше.
Например, если вы используете ленивую загрузку с помощью nhibernate, это может привести к загрузке графика, когда / при необходимости.