Używanie IQueryable z Linq

Jaki jest pożytek IQueryable W kontekście LINQ?

Jest używany do rozwijania metod rozszerzeń lub w jakimkolwiek innym celu?

Author: SteveC, 2009-10-16

4 answers

Odpowiedź marca Gravella jest bardzo kompletna, ale pomyślałem, że dodam coś na ten temat również z punktu widzenia użytkownika...


Główna różnica, z punktu widzenia użytkownika, polega na tym, że gdy używasz IQueryable<T> (z dostawcą, który obsługuje wszystko poprawnie), możesz zaoszczędzić wiele zasobów.

Na przykład, jeśli pracujesz ze zdalną bazą danych, z wieloma systemami ORM, masz możliwość pobierania danych z tabeli na dwa sposoby, jeden z nich zwraca IEnumerable<T> i taki, który zwraca IQueryable<T>. Powiedzmy, na przykład, masz tabelę produktów i chcesz uzyskać wszystkie produkty, których koszt wynosi > $25.

Jeśli zrobisz:

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

To, co się tutaj dzieje, to baza danych ładuje wszystkie produkty i przekazuje je przez przewód do twojego programu. Następnie program filtruje dane. Zasadniczo baza danych wykonuje SELECT * FROM Products i zwraca każdy produkt do ciebie.

Z prawej IQueryable<T> dostawcy, z drugiej strony, można do:

 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 wykonywany SQL będzie 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....

 448
Author: Reed Copsey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:10

W istocie jego zadanie jest bardzo podobne do IEnumerable<T> - reprezentowanie źródła danych z zapytaniem - różnica polega na tym, że różne metody LINQ (na Queryable) mogą być bardziej szczegółowe, aby budować zapytanie za pomocą drzew Expression zamiast delegatów (czego używa Enumerable).

Drzewa wyrażeń mogą być sprawdzane przez wybranego dostawcę LINQ i przekształcane w rzeczywiste zapytanie - chociaż jest to czarna sztuka sama w sobie.

To naprawdę zależy od ElementType, Expression oraz Provider - ale w rzeczywistości rzadko musisz dbać o to jako użytkownik . Tylko wykonawca 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 jest DataContext, który reprezentuje naszą wrapper bazy danych. Zazwyczaj ma to właściwość dla tabeli (na przykład Customers), a tabela implementuje IQueryable<Customer>. Ale nie używamy tak dużo bezpośrednio; consider:

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

To 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, statyczne metody na Queryable przyjmują drzewa wyrażeń, które-zamiast zwykłego IL-są kompilowane do modelu obiektowego. Na przykład-po prostu patrząc na "gdzie", daje nam to 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że być rozszarpany, sprawdzany pod kątem jego znaczenia i poskładany ponownie przez generator TSQL-dając coś w stylu:

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

(łańcuch może skończyć się jako parametr; nie pamiętam)

Nic z tego nie byłoby możliwe, gdybyśmy tylko użyli delegata. I to jest celem Queryable / IQueryable<T>: zapewnia punkt wejścia do użycia drzew wyrażeń.

To wszystko jest bardzo skomplikowane, więc dobrze, że kompilator sprawia, że jest to dla nas miłe i łatwe.

Aby uzyskać więcej informacji, zajrzyj do "C # in Depth " lub " LINQ w działaniu ", które zawierają omówienie tych tematów.

 170
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-26 21:35:54

Chociaż Reed Copseyi Marc Gravell już opisali o IQueryable (a także IEnumerable) wystarczająco, mI chce dodać trochę więcej, podając mały przykład na IQueryable i IEnumerable, jak wielu użytkowników o to prosiło{[38]]}

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)
Jest to klucz podstawowy(PersonId) tabeli Employee jest również kluczem forgein (personid) tabeli Person

Następny dodałem ado.net model encji w mojej aplikacji i utwórz poniższy serwis class on that

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

Wyjście jest takie samo dla obu oczywiście

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

Więc pytanie brzmi, co/gdzie jest różnica? Nie wydaje się masz jakąś różnicę, prawda? Naprawdę!!

Przyjrzyjmy się zapytaniom sql generowanym i wykonywanym przez entity framwork 5 w tym okresie

IQueryable execution part

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

Wykonanie zbiorcze część

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

/* 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ń, niech zgadnę te i spróbuj odpowiedzieć na nie

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

Dowiedzmy się kilku punktów tutaj,

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

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

Dlaczego? Ponieważ obie funkcje IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable i IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable z SomeServiceClass zawiera jeden wspólny wiersz w zapytaniach linq

where employeesToCollect.Contains(e.PersonId)

Than why is the AND (N'M' = [Extent1].[Gender]) brakuje części w IEnumerable części wykonawczej, podczas gdy w obu wywołaniach funkcji użyliśmy programu Where(i => i.Gender == "M") in.cs '

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

Co robi encja Framwork gdy wywołana jest metoda IQueryable, wykonuje polecenie linq napisane wewnątrz metody i próbuje dowiedzieć się, czy więcej wyrażeń linq jest zdefiniowanych w resultset, następnie zbiera wszystkie zapytania LINQ zdefiniowane do wynik musi pobrać i skonstruować bardziej odpowiednie zapytanie sql do wykonania.

Zapewnia wiele korzyści, takich jak:]}
  • tylko te wiersze wypełnione przez sql server, które mogą być poprawne przez wykonanie całego zapytania linq
  • pomaga w działaniu sql server, nie wybierając niepotrzebnych wierszy
  • koszt sieci zmniejsz

Jak tutaj w przykładzie SQL server zwrócił do aplikacji tylko dwa wiersze po IQueryable execution ' ale zwrócił trzy wiersze dla mnogości zapytań dlaczego?

W przypadku metody IEnumerable, entity framework pobierał instrukcję linq napisaną wewnątrz metody i konstruował zapytanie sql, gdy wynik wymagał pobrania. nie zawiera części rest linq do konstruowania zapytania sql. Podobnie jak tutaj żadne filtrowanie nie jest wykonywane w sql server na kolumnie gender.

Ale wyjścia są takie same? Ponieważ ' IEnumerable filtruje wynik dalej na poziomie aplikacji po pobraniu wyniku z sql serwer

Więc, co ktoś powinien wybrać? Osobiście wolę definiować wynik funkcji jako IQueryable<T>, ponieważ jest wiele korzyści, które ma ponad IEnumerable Jak, można połączyć dwie lub więcej funkcji IQueryable, które generują bardziej specyficzny skrypt do SQL server.

Tutaj w przykładzie można zobaczyć IQueryable Query(IQueryableQuery2) generuje bardziej specyficzny skrypt niż IEnumerable query(IEnumerableQuery2), który jest o wiele bardziej akceptowalny z mojego punktu widzenia.

 12
Author: Moumit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-21 13:05:28

Pozwala na dalsze zapytania dalej w dół linii. Gdyby to było poza granicą usługi powiedzmy, wtedy użytkownik tego IQueryable obiektu będzie mógł zrobić więcej z nim.

Na przykład, jeśli używasz leniwego ładowania z nhibernate, może to spowodować, że graph zostanie załadowany, gdy zajdzie taka potrzeba.

 1
Author: dove,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-16 15:29:24