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

257

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

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

user190560
источник

Ответы:

508

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


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

Рид Копси
источник
9
Где определение "GetQueryableProducts ();"?
Панкадж
12
@StackOverflowUser Предполагается, что это будет любой метод, который возвращает IQueryable<Product>- будет специфичным для вашего ORM или репозитория и т. Д.
Рид Копси
право. но вы упомянули, где предложение после этого вызова функции. Таким образом, система все еще не знает о фильтре. Я имею в виду, что это все еще получить все записи продуктов. право?
Панкадж
@StackOverflowUser Нет - в этом прелесть IQueryable <T> - его можно настроить для оценки при получении результатов - это означает, что выражение Where, используемое после факта, все равно будет преобразовано в оператор SQL, выполняемый на сервере, и тянуть только необходимые элементы через провод ...
Рид Копси
40
@Testing Вы на самом деле все еще не идете в БД. Пока вы на самом деле не перечислите результаты (то есть: используете foreachили вызовите ToList()), вы фактически не попадете в БД.
Рид Копси
188

По сути, его работа очень похожа на 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 в действии », которые оба охватывают эти темы.

Марк Гравелл
источник
2
Если вы не возражаете, вы можете обновить меня простым понятным примером (если у вас есть время).
user190560 16.10.09
Можете ли вы объяснить «Где определение» GetQueryableProducts (); "?" в ответе мистера Рида Копси
Панкадж
Понравилась строка перевода выражений на запрос «Черное искусство само по себе» ... много правды в этом
afreeland
Почему все это было бы невозможно, если бы вы использовали делегата?
Дэвид Клемпфнер
1
@Backwards_Dave, потому что делегат указывает (по существу) на IL, а IL недостаточно выразителен, чтобы разумно попытаться деконструировать намерение в достаточной степени для построения SQL. IL также допускает слишком много вещей - то есть большинство вещей, которые могут быть выражены в IL, не могут быть выражены в ограниченном синтаксисе, который разумно превратить в такие вещи, как SQL
Марк Грэвелл
17

Хотя Рид Копси и Марк Гравелл уже достаточно подробно описали 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> GetEmployeeAndPersonDetailIEnumerableof 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)который на мой взгляд более приемлем.

Moumit
источник
2

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

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

голубь
источник