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

Какая польза от IQueryable в контексте LINQ?

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

4 ответа

Решение

Ответ Марка Гравелла очень полный, но я решил добавить кое-что об этом и с точки зрения пользователя...


Основное отличие, с точки зрения пользователя, заключается в том, что при использовании 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 записи по сети вместо 20000...

По сути, его работа очень похожа на IEnumerable<T> - представлять запрашиваемый источник данных - разница в том, что различные методы LINQ (на 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 in Action", которые оба охватывают эти темы.

Хотя Рид Копси и Марк Гравелл уже рассказывали о 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 из SomeServiceClass содержит одну общую строку в запросах linq

where employeesToCollect.Contains(e.PersonId)

Чем почему тоAND (N'M' = [Extent1].[Gender]) часть отсутствует в IEnumerable часть выполнения, в то время как в обоих вызовах функций мы использовали Where(i => i.Gender == "M") inprogram.cs`

Теперь мы находимся в точке, где разница между IQueryable а также IEnumerable

Что делает фреймворк, когда IQueryable Вызванный метод, он обрабатывает оператор linq, написанный внутри метода, и пытается выяснить, определено ли больше выражений linq в результирующем наборе, затем он собирает все определенные запросы linq до тех пор, пока результат не должен быть извлечен, и создает более подходящий SQL-запрос для выполнения.

Это обеспечивает много преимуществ, таких как,

  • только те строки, заполненные сервером sql, которые могут быть действительными при выполнении всего запроса 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, это может привести к загрузке графика, когда / при необходимости.

Другие вопросы по тегам