Каково использование IQueryable
в контексте LINQ?
Используется ли он для разработки методов расширения или для любых других целей?
Каково использование IQueryable
в контексте LINQ?
Используется ли он для разработки методов расширения или для любых других целей?
Ответ Марка Гравелла очень полный, но я думал, что я добавлю что-то об этом с точки зрения пользователя, а также...
Основное отличие, с точки зрения пользователя, состоит в том, что при использовании IQueryable<T>
(с провайдером, который поддерживает все правильно) вы можете сэкономить много ресурсов.
Например, если вы работаете с удаленной базой данных со многими системами ORM, у вас есть возможность извлекать данные из таблицы двумя способами: с возвратом IEnumerable<T>
и с возвратом IQueryable<T>
, Скажем, например, у вас есть таблица Products, и вы хотите получить все продукты, стоимость которых составляет > $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
.
Из вашего POV как разработчика это выглядит одинаково. Однако с точки зрения производительности вы можете вернуть только 2 записи по сети вместо 20 000.
По существу, его работа очень похожа на IEnumerable<T>
- для представления запрашиваемого источника данных - разница в том, что различные методы LINQ (на Queryable
) могут быть более конкретными, для построения запроса с использованием деревьев Expression
а не делегатов (что используется Enumerable
).
Деревья выражений могут быть проверены выбранным вами поставщиком LINQ и превращены в фактический запрос - хотя это черное искусство само по себе.
Это действительно до ElementType
, Expression
и Provider
, но на самом деле вам редко приходится заботиться об этом как пользователь. Только разработчик LINQ должен знать детали gory.
Re комментарии; Я не совсем уверен, что вы хотите в качестве примера, но рассмотрите 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);
}
}
это становится (с помощью компилятора С#):
var qry = ctx.Customers.Where(cust => cust.Region == "North")
.Select(cust => new { cust.Id, cust.Name });
который снова интерпретируется (компилятором С#) следующим образом:
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>
: она обеспечивает точку входа для использования деревьев выражений.
Все это очень сложно, поэтому хорошая работа заключается в том, что компилятор делает это приятным и легким для нас.
Для получения дополнительной информации смотрите " С# в глубине" или " LINQ в действии", оба из которых обеспечивают освещение этих тем.
Хотя Reed Copsey и Марк Гравелл уже описал примерно IQueryable(and also IEnumerable)
, хотя я хочу добавить немного больше здесь, предоставляя small example on IQueryable and 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)
Primary key(PersonId)
таблицы Employee
также forgein key(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();
}
}
output is same for both
очевидно
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-запросы, сгенерированные и выполняемые сущностью каркасная конструкция 5 в течение этого периода
IQueryable execution part
--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 execution part
--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)
Common script for both execution part
/* 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
*/
Итак, у вас сейчас мало вопросов, позвольте мне угадать их и попытаться ответить им
Why different script is generated for same result?
Здесь можно найти некоторые моменты,
все запросы имеют одну общую часть
WHERE [Extent1].[PersonId] IN (0,1,2,3)
почему? Поскольку обе функции IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable
и
IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable
of SomeServiceClass
содержит один common line in linq queries
where employeesToCollect.Contains(e.PersonId)
Чем почему
AND (N'M' = [Extent1].[Gender])
отсутствует в IEnumerable execution part
, в то время как в обоих вызовах функции мы использовали Where(i => i.Gender == "M")
в program.cs
Теперь мы находимся в точке, где разница между
IQueryable
иIEnumerable
Что entity framwork
делает при вызове метода IQueryable
, он взял linq statement written inside the method
и попытался выяснить, есть ли more linq/expression is defind on the resultset
, он собирает все запросы linq, определенные до тех пор, пока результат не будет получен и constructs more appropriate sql query to execute
.
Он предоставляет множество преимуществ, таких как
как здесь, в примере sql-сервер, возвращаемый только в приложение two rows after IQueryable execution
, но returned THREE rows for IEnumerable query
почему?
В случае метода IEnumerable
entity framework
принял оператор linq, написанный внутри метода, и построит sql-запрос, когда результат нужно извлечь. it does not include rest linq part to constructs the sql query
. Как и здесь, фильтрация не выполняется в sql-сервере в столбце gender
.
Но выходы одинаковы? Поскольку "IEnumerable" фильтрует результат дальше на уровне приложения после получения результата из sql server
SO,, что выбрать?
Я лично предпочитаю определять результат функции как IQueryable<T>
, потому что есть много преимуществ, которые он имеет над "IEnumerable", например join two or more IQueryable function
, которые генерируют более специфический script на sql-сервер.
Здесь в примере вы можете увидеть IQueryable Query(IQueryableQuery2)
сгенерировать более конкретный script, чем IEnumerable query(IEnumerableQuery2)
, что гораздо более приемлемо в my point of view
.
Это позволяет продолжить запрос дальше по строке. Если это было за пределами границы службы, скажите, тогда пользователю этого объекта IQueryable будет разрешено делать с ним больше.
Например, если вы использовали ленивую загрузку с помощью nhibernate, это может привести к загрузке графика при необходимости.