Використання IQueryable з Linq


256

Яке використання IQueryableв контексті LINQ?

Чи використовується вона для розробки методів розширення чи для будь-якої іншої мети?

Відповіді:


507

Відповідь Марка Гравелла дуже повна, але я думав, що я додам щось про це з точки зору користувача, а також ...


Основна відмінність, з точки зору користувача, полягає в тому, що при використанні 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.

З вашого POV як розробника це виглядає так само. Однак, з точки зору продуктивності, ви можете повернути лише 2 записи в мережі замість 20 000….


9
Де є визначення "GetQueryableProducts ();"?
Панкай

12
@StackOverflowUser Це призначений для будь-якого методу, який повертає IQueryable<Product>- буде специфічним для вашої ORM або сховища тощо.
Reed Copsey

правильно. але ви згадали, куди після цієї функції викликати пункт. Тож система все ще не знає про фільтр. Я маю на увазі, що це все ще отримує всі записи продуктів. правильно?
Панкай

@StackOverflowUser Ні - це краса IQueryable <T> - це може бути налаштовано для оцінки, коли ви отримаєте результати - це означає, що пункт «Де», який використовується після факту, все одно буде переведений в оператор SQL, запущений на сервері, і тягніть лише необхідні елементи по дроту ...
Рід Копсей

40
@ Тестування Ви насправді все ще не збираєтеся в БД. Поки ви фактично не перерахуєте результати (наприклад, використовуйте a foreachабо виклик ToList()), ви фактично не потрапляєте до БД.
Рід Копсей

188

По суті, його завдання дуже схоже на IEnumerable<T>- представляти джерело даних, що підлягає опису, - різниця полягає в тому, що різні методи LINQ (увімкнено Queryable) можуть бути більш конкретними, будувати запит, використовуючи Expressionдерева, а не делегати (для чого Enumerableвикористовується).

Дерева виразів може перевірити обраний вами постачальник LINQ і перетворити його на фактичний запит - хоча це вже саме чорне мистецтво.

Це дійсно до того ElementType, Expressionі Provider- але насправді вам рідко про це потрібно піклуватися як про користувача . Тільки реалізатор LINQ повинен знати деталі gory.


Re коментарі; Я не зовсім впевнений, що ви хочете на прикладі, але врахуйте LINQ до 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 в дії ", обидва з яких забезпечують висвітлення цих тем.


2
Якщо ви не заперечуєте, чи можете ви оновити мене простим зрозумілим прикладом (якщо у вас є час).
user190560

Чи можете ви пояснити "Де визначення" GetQueryableProducts (); "?" у відповідь містера Ріда Копсі
Панкай

Насолоджувався рядок перекладу виразів на запит - "чорне мистецтво саме по собі" ... багато правди до цього
afreeland

Чому б нічого з цього не було можливим, якби ви використовували делегата?
Девід Клемффнер

1
@Backwards_Dave, оскільки пункти делегата (по суті) на IL, а IL недостатньо виразні, щоб зробити розумним спробу деконструювати намір достатньо для створення SQL. IL також дозволяє занадто багато речей - тобто більшість речей, які можна виразити в IL, не можна було б виразити в обмеженому синтаксисі, що розумно перетворити на такі речі, як SQL
Marc Gravell

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, згенеровані та виконані фреймворком сутності 5 за цей період

Частина виконання IQueyable

--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]

IE численні частини виконання

--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

Що робить framwork об'єкта, коли викликається IQueryableметод, він надто виписує заяву linq, написане всередині методу, і намагається з’ясувати, чи визначено більше виразів linq на наборі результатів, після чого він збирає всі запити linq, визначені, доки результат не повинен отримати та побудувати більш відповідний sql запит для виконання.

Це дає багато переваг, таких як,

  • лише ті рядки, заселені сервером sql, які можуть бути дійсними при виконанні всього запиту linq
  • допомагає продуктивності сервера sql, не вибираючи зайвих рядків
  • вартість мережі зменшиться

як ось у прикладі sql сервер повернувся до програми лише два ряди після виконання IQueryable`, але повернув ТРИ рядок для IErumerable query чому?

У випадку IEnumerableметоду, структура сутності приймає оператор linq, написаний всередині методу, і будує запит sql, коли результат потрібно отримати. вона не включає частину linq спокою для побудови запиту sql. Як і тут, фільтрація не проводиться на сервері sql на стовпці gender.

Але результати однакові? Тому що "IEnumerable фільтрує результат далі на рівні програми після отримання результату з сервера sql

Так, що хтось повинен вибрати? Я особисто вважаю за краще визначити результат функції, IQueryable<T>тому що є багато переваг, які він має, IEnumerableяк, ви можете приєднатися до двох або більше функцій IQueryable, які генерують більш конкретний сценарій на сервері sql

Тут, наприклад, можна побачити IQueryable Query(IQueryableQuery2)генерований більш конкретний сценарій, ніж IEnumerable query(IEnumerableQuery2)набагато більш прийнятний з моєї точки зору.


2

Це дозволяє подавати запит далі вниз по лінії. Якби це було за межами сервісної межі, скажімо, користувачеві цього об'єкта IQueryable було б дозволено більше робити з ним.

Наприклад, якщо ви використовували ледачу завантаження з нібернатами, це може призвести до завантаження графіка, коли / при необхідності.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.