Używanie IQueryable z Linq

256

Jakie jest zastosowanie IQueryablew kontekście LINQ?

Czy jest używany do opracowywania metod rozszerzenia lub w jakimkolwiek innym celu?

użytkownik190560
źródło

Odpowiedzi:

507

Odpowiedź Marca Gravella jest bardzo kompletna, ale pomyślałem, że dodam coś o tym również z punktu widzenia użytkownika ...


Główną różnicą z punktu widzenia użytkownika jest to, że kiedy korzystasz IQueryable<T>(z dostawcą, który poprawnie obsługuje rzeczy), możesz zaoszczędzić wiele zasobów.

Na przykład, jeśli pracujesz przeciwko zdalnej bazie danych, z wieloma systemami ORM, masz opcję pobierania danych z tabeli na dwa sposoby, jeden zwracający IEnumerable<T>, a drugi zwracający IQueryable<T>. Załóżmy na przykład, że masz tabelę Produkty i chcesz uzyskać wszystkie produkty, których koszt wynosi> 25 USD.

Jeśli zrobisz:

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

To, co się tutaj dzieje, polega na tym, że baza danych ładuje wszystkie produkty i przekazuje je do twojego programu. Twój program następnie filtruje dane. Zasadniczo baza danych wykonuje SELECT * FROM Productsi zwraca KAŻDY produkt.

Z odpowiednim IQueryable<T>dostawcą możesz natomiast:

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

Kod wygląda tak samo, ale różnica polega na tym, że SQL zostanie wykonany SELECT * FROM Products WHERE Cost >= 25.

Z twojego POV jako programisty wygląda to tak samo. Jednak z punktu widzenia wydajności możesz zwrócić tylko 2 rekordy w sieci zamiast 20 000 ....

Reed Copsey
źródło
9
Gdzie jest definicja „GetQueryableProducts ();”?
Pankaj
12
@StackOverflowUser Ma to być dowolna metoda, która zwraca IQueryable<Product>- byłaby specyficzna dla Twojego ORM lub repozytorium itp.
Reed Copsey
dobrze. ale wspomniano o klauzuli where po wywołaniu tej funkcji. System nadal nie wie o filtrze. Mam na myśli, że nadal pobiera wszystkie zapisy produktów. dobrze?
Pankaj
@StackOverflowUser Nie - to piękno IQueryable <T> - można go skonfigurować, aby oceniał, kiedy otrzymasz wyniki - co oznacza, że ​​klauzula Where, używana po fakcie, nadal zostanie przetłumaczona na instrukcję SQL uruchomioną na serwerze i ciągnij tylko wymagane elementy przez drut ...
Reed Copsey,
40
@ Testowanie W rzeczywistości nadal nie idziesz do DB. Dopóki nie wyliczysz wyników (tj. Użyj foreachlub zadzwoń ToList()), tak naprawdę nie trafisz do bazy danych.
Reed Copsey,
188

Zasadniczo jego zadanie jest bardzo podobne do IEnumerable<T>- reprezentowania źródła danych, które można zapytać - różnica polega na tym, że różne metody LINQ (włączone Queryable) mogą być bardziej szczegółowe, aby zbudować zapytanie przy użyciu Expressiondrzew, a nie delegatów (co Enumerablewykorzystuje).

Drzewa wyrażeń mogą być sprawdzane przez wybranego dostawcę LINQ i przekształcane w rzeczywiste zapytanie - chociaż samo w sobie jest czarną sztuką.

To naprawdę zależy od tego ElementType, Expressioni Provider- ale w rzeczywistości rzadko musisz się tym przejmować jako użytkownik . Tylko osoba wdrażająca LINQ musi znać krwawe szczegóły.


Re komentarze; Nie jestem do końca pewien, czego chcesz na przykład, ale rozważ LINQ-to-SQL; centralnym obiektem tutaj jest DataContextsymbol reprezentujący nasze opakowanie bazy danych. Zwykle ma właściwość na tabelę (na przykład Customers) i implementuje tabelę IQueryable<Customer>. Ale nie używamy tak dużo bezpośrednio; rozważać:

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

staje się (przez kompilator C #):

var qry = ctx.Customers.Where(cust => cust.Region == "North")
                .Select(cust => new { cust.Id, cust.Name });

który jest ponownie interpretowany (przez kompilator C #) jako:

var qry = Queryable.Select(
              Queryable.Where(
                  ctx.Customers,
                  cust => cust.Region == "North"),
              cust => new { cust.Id, cust.Name });

Co ważne, metody statyczne w Queryabledrzewach wyrażeń, które - zamiast zwykłej IL, są kompilowane do modelu obiektowego. Na przykład - wystarczy spojrzeć na „Where”, co daje nam coś porównywalnego do:

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

Czy kompilator nie zrobił dla nas wiele? Ten model obiektowy można rozerwać na części, sprawdzić pod kątem jego znaczenia i złożyć ponownie w całość za pomocą generatora TSQL - dając coś takiego:

 SELECT c.Id, c.Name
 FROM [dbo].[Customer] c
 WHERE c.Region = 'North'

(ciąg może skończyć jako parametr; nie pamiętam)

Nie byłoby to możliwe, gdybyśmy właśnie skorzystali z usług delegata. I to jest punkt Queryable/ IQueryable<T>: zapewnia punkt wejścia do korzystania z drzew wyrażeń.

Wszystko to jest bardzo złożone, więc kompilator sprawia, że ​​jest to dla nas przyjemne i łatwe.

Aby uzyskać więcej informacji, zobacz „ C # in Depth ” lub „ LINQ in Action ”, z których oba zapewniają omówienie tych tematów.

Marc Gravell
źródło
2
Jeśli nie masz nic przeciwko, możesz zaktualizować mnie prostym, zrozumiałym przykładem (jeśli masz czas).
user190560,
Czy możesz wyjaśnić „Gdzie jest definicja” GetQueryableProducts (); „?” w odpowiedzi pana Reeda Copseya
Pankaj,
Podobało się, że tłumaczenie wyrażeń na zapytanie jest „czarną sztuką samą w sobie” ... dużo prawdy na ten temat
Grecji
Dlaczego nie byłoby to możliwe, gdybyś użył delegata?
David Klempfner,
1
@Backwards_Dave, ponieważ delegat wskazuje (zasadniczo) na IL, a IL nie jest wystarczająco ekspresywna, aby uzasadnić próbę zdekonstruowania zamiaru wystarczającego do zbudowania SQL. IL pozwala również na zbyt wiele rzeczy - tzn. Większość rzeczy, które można wyrazić w IL, nie może być wyrażona w ograniczonej składni, że rozsądnie jest zmienić się w takie rzeczy jak SQL
Marc Gravell
17

Chociaż Reed Copsey i Marc Gravell już opisali wystarczająco dużo IQueryable(i również IEnumerable), chcę dodać tutaj trochę więcej, podając mały przykład IQueryablei IEnumerablewielu użytkowników o to poprosiło

Przykład : Utworzyłem dwie tabele w bazie danych

   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)

Klucz podstawowy ( PersonId) w tabeli Employeejest również kluczem kuźni ( personid) w tabeliPerson

Następnie dodałem model encji ado.net do mojej aplikacji i utworzyłem poniżej klasę usług

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

zawierają ten sam linq. Wywołał program.csjak zdefiniowano poniżej

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

Wynik jest oczywiście taki sam dla obu

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

Pytanie brzmi, jaka jest / gdzie jest różnica? Wydaje się, że nie ma to żadnej różnicy, prawda? Naprawdę!!

Przyjrzyjmy się zapytaniom sql wygenerowanym i wykonanym przez zespół jednostek 5 w tym okresie

I Część możliwa do wykonania

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

IEnumerowalna część wykonania

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

Wspólny skrypt dla obu części wykonania

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

Więc masz teraz kilka pytań, pozwól mi je odgadnąć i spróbuj na nie odpowiedzieć

Dlaczego generowane są różne skrypty dla tego samego wyniku?

Dowiedzmy się tutaj kilka punktów,

wszystkie zapytania mają jedną wspólną część

WHERE [Extent1].[PersonId] IN (0,1,2,3)

czemu? Ponieważ zarówno funkcję IQueryable<Employee> GetEmployeeAndPersonDetailIQueryablei IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerableod SomeServiceClasszawiera jedną wspólną linię zapytań LINQ

where employeesToCollect.Contains(e.PersonId)

Dlaczego więc AND (N'M' = [Extent1].[Gender])brakuje części w IEnumerableczęści wykonawczej, podczas gdy w obu wywołaniach funkcji użyliśmy Where(i => i.Gender == "M") inprogramu.cs`

Teraz jesteśmy w punkcie, w którym pojawiła się różnica między IQueryablei IEnumerable

To, co robi ramka encji, gdy wywoływana IQueryablemetoda, działa na instrukcję linq zapisaną w metodzie i próbuje dowiedzieć się, czy w zestawie wyników zdefiniowano więcej wyrażeń linq, a następnie gromadzi wszystkie zdefiniowane zapytania linq, dopóki wynik nie będzie wymagał pobrania i konstruuje bardziej odpowiednie sql zapytanie do wykonania.

Zapewnia wiele korzyści, takich jak

  • tylko te wiersze wypełnione przez serwer sql, które mogą być poprawne przez całe wykonanie zapytania linq
  • poprawia wydajność serwera SQL, nie wybierając niepotrzebnych wierszy
  • obniż koszty sieci

jak tutaj w przykładzie serwer sql wrócił do aplikacji tylko dwa wiersze po wykonaniu IQueryable`, ale zwrócił TRZY wiersze dla zapytania IEnumerable dlaczego?

W przypadku IEnumerablemetody, środowisko encji wzięło instrukcję linq zapisaną w metodzie i konstruuje zapytanie SQL, gdy wynik musi zostać pobrany. nie zawiera reszty linq do konstruowania zapytania SQL. Podobnie jak tutaj, nie wykonuje się filtrowania na serwerze SQL w kolumnie gender.

Ale wyniki są takie same? Ponieważ „IEnumerable filtruje wynik dalej na poziomie aplikacji po pobraniu wyniku z serwera SQL

Więc co powinien wybrać ktoś? Ja osobiście wolę zdefiniować wynik funkcji, ponieważ IQueryable<T>ponieważ ma wiele zalet, na IEnumerableprzykład, możesz dołączyć dwie lub więcej funkcji IQueryable, które generują bardziej szczegółowy skrypt do serwera SQL.

W tym przykładzie widać, że IQueryable Query(IQueryableQuery2)generuje bardziej szczegółowy skrypt, IEnumerable query(IEnumerableQuery2)który jest o wiele bardziej akceptowalny z mojego punktu widzenia.

Moumit
źródło
2

Umożliwia dalsze zapytania w dalszej linii. Jeśli jest to poza granicami usługi, powiedzmy, że użytkownik tego obiektu IQueryable będzie mógł zrobić z nim więcej.

Na przykład, jeśli używasz leniwego ładowania z nhibernate, może to powodować ładowanie wykresu, gdy / jeśli to konieczne.

gołąb
źródło