Jakie jest zastosowanie IQueryable
w kontekście LINQ?
Czy jest używany do opracowywania metod rozszerzenia lub w jakimkolwiek innym celu?
Jakie jest zastosowanie IQueryable
w kontekście LINQ?
Czy jest używany do opracowywania metod rozszerzenia lub w jakimkolwiek innym celu?
Odpowiedzi:
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 Products
i 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 ....
IQueryable<Product>
- byłaby specyficzna dla Twojego ORM lub repozytorium itp.
foreach
lub zadzwoń ToList()
), tak naprawdę nie trafisz do bazy danych.
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 Expression
drzew, a nie delegatów (co Enumerable
wykorzystuje).
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
, Expression
i 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 DataContext
symbol 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 Queryable
drzewach 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.
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 IQueryable
i IEnumerable
wielu 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 Employee
jest 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.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();
}
}
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> GetEmployeeAndPersonDetailIQueryable
i
IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable
od SomeServiceClass
zawiera jedną wspólną linię zapytań LINQ
where employeesToCollect.Contains(e.PersonId)
Dlaczego więc
AND (N'M' = [Extent1].[Gender])
brakuje części w IEnumerable
części wykonawczej, podczas gdy w obu wywołaniach funkcji użyliśmy Where(i => i.Gender == "M") in
programu.cs`
Teraz jesteśmy w punkcie, w którym pojawiła się różnica między
IQueryable
iIEnumerable
To, co robi ramka encji, gdy wywoływana IQueryable
metoda, 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
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 IEnumerable
metody, ś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 IEnumerable
przykł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.
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.