Utilizarea IQueryable cu Linq (Programare, C#, Linq, Iqueryable)

user190560 a intrebat.

Care este utilizarea lui IQueryable în contextul LINQ?

Este utilizat pentru dezvoltarea metodelor de extensie sau în orice alt scop?

4 răspunsuri
Reed Copsey

Răspunsul lui Marc Gravell este foarte complet, dar m-am gândit să adaug și eu ceva despre acest lucru din punctul de vedere al utilizatorului…


Principala diferență, din punctul de vedere al utilizatorului, este că, atunci când utilizați IQueryable<T> (cu un furnizor care suportă lucrurile corect), puteți economisi o mulțime de resurse.

De exemplu, dacă lucrați cu o bază de date la distanță, cu multe sisteme ORM, aveți opțiunea de a prelua date dintr-o tabelă în două moduri, unul care returnează IEnumerable<T>, și unul care returnează un IQueryable<T>. Să zicem, de exemplu, că aveți un tabel Produse și doriți să obțineți toate produsele al căror cost este >25$.

Dacă faceți acest lucru:

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

Ceea ce se întâmplă aici este că baza de date încarcă toate produsele și le transmite prin cablu către programul dvs. Programul dvs. filtrează apoi datele. În esență, baza de date face o SELECT * FROM Productsși vă returnează TOATE produsele.

Cu ajutorul unei IQueryable<T> furnizor, pe de altă parte, puteți face:

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

Codul arată la fel, dar diferența aici este că SQL-ul executat va fi SELECT * FROM Products WHERE Cost >= 25.

Din punctul tău de vedere ca dezvoltator, acest lucru arată la fel. Cu toate acestea, din punct de vedere al performanței, este posibil să returnați doar 2 înregistrări în rețea în loc de 20.000….

Comentarii

  • Unde este definiția „GetQueryableProducts(); „? –  > Por Pankaj.
  • @StackOverflowUser Este vorba de orice metodă care returnează un IQueryable<Product> – ar fi specific ORM-ului sau depozitului dvs. etc. –  > Por Reed Copsey.
  • corect. dar ați menționat clauza where după acest apel de funcție. Deci, sistemul încă nu știe de filtru. Vreau să spun că tot va prelua toate înregistrările de produse, nu? –  > Por Pankaj.
  • @StackOverflowUser Nu – aceasta este frumusețea lui IQueryable<T> – poate fi configurat pentru a evalua atunci când obțineți rezultatele – ceea ce înseamnă că clauza Where, utilizată după ce a fost utilizată, va fi tradusă în continuare într-o instrucțiune SQL executată pe server și va trage doar elementele necesare prin cablu… –  > Por Reed Copsey.
  • 41

  • @Testing De fapt, încă nu te duci la BD. Până când nu veți enumera efectiv rezultatele (adică: utilizați un foreach, sau apelați ToList()), nu accesezi de fapt BD. –  > Por Reed Copsey.
Marc Gravell

În esență, sarcina sa este foarte asemănătoare cu IEnumerable<T> – de a reprezenta o sursă de date interogabilă – diferența fiind că diferitele metode LINQ (pe Queryable) pot fi mai specifice, pentru a construi interogarea folosind Expression arbori mai degrabă decât delegați (ceea ce este ceea ce Enumerable utilizează).

Arborii de expresii pot fi inspectați de către furnizorul LINQ ales de dvs. și transformați într-o real interogare – deși aceasta este o artă neagră în sine.

Acest lucru depinde în realitate de ElementType, Expression și Provider – dar, în realitate, trebuie să rareori trebuie să vă intereseze acest lucru ca utilizator. Doar un LINQ care implementează LINQ trebuie să cunoască detaliile sângeroase.


În ceea ce privește comentariile; nu sunt foarte sigur de ceea ce doriți ca exemplu, dar luați în considerare LINQ-to-SQL; obiectul central aici este un obiect DataContext, care reprezintă învelișul bazei noastre de date. Acesta are de obicei o proprietate pentru fiecare tabel (de exemplu, Customers), iar un tabel implementează IQueryable<Customer>. Dar noi nu folosim prea mult acest lucru în mod direct; luați în considerare:

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

aceasta devine (de către compilatorul C#):

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

care este din nou interpretat (de către compilatorul C#) ca:

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

Este important de menționat că metodele statice de pe Queryable iau arbori de expresii, care – mai degrabă decât IL regulat, sunt compilate într-un model de obiect. De exemplu – doar uitându-ne la „Where”, acest lucru ne dă ceva comparabil cu:

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

Nu cumva compilatorul a făcut multe pentru noi? Acest model de obiect poate fi despărțit, inspectat pentru a afla ce înseamnă și asamblat din nou de către generatorul TSQL – ceea ce dă ceva de genul:

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

(șirul de caractere ar putea sfârși ca un parametru; nu-mi amintesc)

Nimic din toate acestea nu ar fi fost posibil dacă am fi folosit doar un delegat. Și acest este scopul lui Queryable / IQueryable<T>: acesta oferă punctul de intrare pentru utilizarea arborilor de expresii.

Toate acestea sunt foarte complexe, așa că este o treabă bună că compilatorul le face ușor și frumos pentru noi.

Pentru mai multe informații, consultați „C# în profunzime” sau „LINQ în acțiune„, ambele oferind o acoperire a acestor subiecte.

Comentarii

  • Dacă nu vă deranjează, puteți să mă puneți la curent cu un exemplu simplu și ușor de înțeles(dacă aveți timp). –  > Por utilizator190560.
  • Puteți explica „Unde este definiția lui „GetQueryableProducts(); ” ?” în răspunsul domnului Reed Copsey – –  > Por Pankaj.
  • Mi-a plăcut replica despre traducerea expresiilor într-o interogare este „o artă neagră în sine”… mult adevăr în asta – –  > Por afreeland.
  • De ce nimic din toate acestea nu ar fi fost posibil dacă ați fi folosit un delegat? –  > Por David Klempfner.
  • @Backwards_Dave pentru că un delegat indică (în esență) IL, iar IL nu este suficient de expresiv pentru a face rezonabilă încercarea de a deconstrui intenția suficient pentru a construi SQL. IL permite, de asemenea, ca prea multe lucruri – adică majoritatea lucrurilor care pot fi exprimate în IL nu ar putea fi exprimate în sintaxa limitată pe care este rezonabil să o transformăm în lucruri precum SQL –  > Por Marc Gravell.
Moumit

Deși Reed Copsey și Marc Gravell au descris deja despre IQueryable (și, de asemenea, și IEnumerable) suficient de mult,m vreau să adaug puțin mai mult aici, oferind un mic exemplu pe IQueryable și IEnumerable deoarece mulți utilizatori au cerut acest lucru

Exemplu: Am creat două tabele în baza de date

   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)

Cheia primară(PersonId) a tabelului Employee este, de asemenea, o cheie falsă(personid) a tabelului Person

În continuare, am adăugat modelul de entitate ado.net în aplicația mea și am creat clasa de serviciu de mai jos pe acesta.

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

acestea conțin același linq. Acesta a fost apelat în program.cs așa cum este definit mai jos

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

Rezultatul este același pentru ambele, evident

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

Așadar, întrebarea este: care este/unde este diferența? Nu pare să existe nicio diferență, nu-i așa? Într-adevăr!

Să aruncăm o privire asupra interogărilor sql generate și executate de entity framwork 5 în această perioadă

Partea de execuție 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]

Partea de execuție 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)

Script comun pentru ambele părți de execuție

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

Deci, acum aveți câteva întrebări, lăsați-mă să le ghicesc și să încerc să vă răspund.

De ce sunt generate scripturi diferite pentru același rezultat?

Haideți să aflăm câteva puncte aici,

toate interogările au o parte comună

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

de ce? Pentru că ambele funcții IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable și IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable din SomeServiceClass conțin o linie comună în interogările linq

where employeesToCollect.Contains(e.PersonId)

Atunci de ce esteAND (N'M' = [Extent1].[Gender]) lipsește în IEnumerable partea de execuție, în timp ce în ambele apeluri de funcții am folosit Where(i => i.Gender == "M") inprogram.cs`

Acum ne aflăm în punctul în care a apărut diferența dintre IQueryable și IEnumerable

Ce face cadrul de entități atunci când un IQueryable este apelată o metodă, aceasta verifică declarația linq scrisă în interiorul metodei și încearcă să afle dacă mai sunt definite mai multe expresii linq în setul de rezultate, apoi adună toate interogările linq definite până la rezultatul care trebuie recuperat și construiește o interogare sql mai adecvată pentru a fi executată.

Aceasta oferă o mulțime de avantaje, cum ar fi,

  • numai acele rânduri populate de serverul sql care ar putea fi valide prin executarea întregii interogări linq
  • ajută performanța serverului sql prin faptul că nu selectează rânduri inutile
  • costul rețelei se reduce

de exemplu, în acest caz, serverul sql a returnat aplicației doar două rânduri după executarea IQueryable, dar a returnat TREI rânduri pentru interogarea IEnumerable, de ce?

În cazul în care IEnumerable entity framework a preluat declarația linq scrisă în cadrul metodei și construiește interogarea sql atunci când este nevoie să se extragă rezultatul. nu include restul linq pentru a construi interogarea sql. De exemplu, aici nu se face nicio filtrare în serverul sql pe coloană. gender.

Dar ieșirile sunt aceleași? Deoarece „IEnumerable filtrează rezultatul în continuare la nivelul aplicației după ce a recuperat rezultatul din serverul sql”.

SO, ce ar trebui să aleagă cineva?Eu personal prefer să definesc rezultatul funcției ca fiind IQueryable<T> deoarece are o mulțime de avantaje față de IEnumerable cum ar fi, de exemplu, posibilitatea de a uni două sau mai multe funcții IQueryable, care generează un script mai specific pentru serverul SQL.

Aici, în exemplu, puteți vedea un IQueryable Query(IQueryableQuery2) generează un script mai specific decât IEnumerable query(IEnumerableQuery2) care este mult mai acceptabil din punctul meu de vedere.

porumbel

Permite o interogare suplimentară mai departe. Dacă acest lucru ar fi dincolo de o limită de serviciu, de exemplu, atunci utilizatorului acestui obiect IQueryable i s-ar permite să facă mai multe cu el.

De exemplu, dacă ați utiliza încărcarea leneșă cu nhibernate, acest lucru ar putea avea ca rezultat încărcarea graficului atunci când/în cazul în care este necesar.