Использование IQueryable с Linq


зачем IQueryable в контексте LINQ?

используется ли он для разработки методов расширения или любой другой цели?

4 225

4 ответа:

Марк Gravell это!--10--> очень полный, но я думал, что добавлю что-то об этом с точки зрения пользователя, а также...


основное отличие, с точки зрения пользователя, заключается в том, что при использовании IQueryable<T> (с поставщиком, который поддерживает вещи правильно), вы можете сэкономить много ресурсов.

например, если вы работаете с удаленной базой данных, с несколькими системами ORM, у вас есть возможность извлечения данных из таблицы в два пути, один из которых возвращает IEnumerable<T>, и тот, который возвращает IQueryable<T>. Скажем, например, у вас есть таблица продуктов, и вы хотите, чтобы получить все продукты, стоимость которых составляет >$25.

если вы:

 IEnumerable<Product> products = myORM.GetProducts();
 var productsOver25 = products.Where(p => p.Cost >= 25.00);

что происходит здесь, - это база данных загружает все продукты, и передает их по сети к вашей программе. Затем ваша программа фильтрует данные. По сути, база данных делает a 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 (on Queryable) может быть более конкретным, чтобы построить запрос с помощью Expression деревья, а не делегаты (что такое Enumerable использует).

деревья выражений могут быть проверены выбранным поставщиком LINQ и превращены в фактический query-хотя это черная искусство само по себе.

это действительно до элемент ElementType,Expression и Provider - но на самом деле ты редко нужно заботиться об этом как пользователей. Только LINQ разработчик нужно знать кровавые детали.


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);
    }
}

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

хотя Рид Copsey и Марк Gravell уже описал о 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 также является ключом forgein (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-запросы, созданные и выполняемые сущностью проводился 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 на SomeServiceClass содержит одну общую строку в запросах linq

where employeesToCollect.Contains(e.PersonId)

чем это AND (N'M' = [Extent1].[Gender]) часть отсутствует в IEnumerable часть выполнения, в то время как в обоих вызовах функции мы использовали

Это позволяет для дальнейшего запроса дальше по линии. Если бы это было за пределами границы службы, то пользователю этого объекта IQueryable было бы разрешено делать с ним больше.

например, если вы использовали ленивую загрузку с nhibernate, это может привести к загрузке графика, когда/если это необходимо.