Para que serve IQueryable
no contexto do LINQ?
É usado para o desenvolvimento de métodos de extensão ou qualquer outro propósito?
Para que serve IQueryable
no contexto do LINQ?
É usado para o desenvolvimento de métodos de extensão ou qualquer outro propósito?
Respostas:
A resposta de Marc Gravell é muito completa, mas pensei em acrescentar algo sobre isso do ponto de vista do usuário também ...
A principal diferença, da perspectiva do usuário, é que, quando você usa IQueryable<T>
(com um provedor que suporta as coisas corretamente), você pode economizar muitos recursos.
Por exemplo, se você estiver trabalhando em um banco de dados remoto, com muitos sistemas ORM, terá a opção de buscar dados de uma tabela de duas maneiras, uma que retorne IEnumerable<T>
e outra que retorne um IQueryable<T>
. Digamos, por exemplo, você tem uma tabela Produtos e deseja obter todos os produtos cujo custo é> US $ 25.
Se você fizer:
IEnumerable<Product> products = myORM.GetProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
O que acontece aqui é que o banco de dados carrega todos os produtos e os transmite para o seu programa. Seu programa então filtra os dados. Em essência, o banco de dados faz um SELECT * FROM Products
e retorna TODOS os produtos para você.
Com o IQueryable<T>
provedor certo , por outro lado, você pode:
IQueryable<Product> products = myORM.GetQueryableProducts();
var productsOver25 = products.Where(p => p.Cost >= 25.00);
O código parece o mesmo, mas a diferença aqui é que o SQL será executado SELECT * FROM Products WHERE Cost >= 25
.
Do seu POV como desenvolvedor, isso parece o mesmo. No entanto, do ponto de vista de desempenho, você só pode retornar 2 registros na rede em vez de 20.000 ....
IQueryable<Product>
- seria específico para o seu ORM ou repositório, etc.
foreach
ou call ToList()
), na verdade você não atinge o banco de dados.
Em essência, seu trabalho é muito semelhante a IEnumerable<T>
- para representar uma fonte de dados consultável - a diferença é que os vários métodos LINQ (ativados Queryable
) podem ser mais específicos, para criar a consulta usando Expression
árvores em vez de delegados (que é o que Enumerable
usa).
As árvores de expressão podem ser inspecionadas pelo provedor LINQ escolhido e transformadas em uma consulta real - embora essa seja apenas uma arte negra.
Isso realmente depende do ElementType
, Expression
e Provider
- mas, na realidade, você raramente precisa se preocupar com isso como usuário . Somente um implementador de LINQ precisa conhecer os detalhes sangrentos.
Re comentários; Não sei bem o que você deseja como exemplo, mas considere o LINQ-to-SQL; o objeto central aqui é a DataContext
, que representa nosso invólucro de banco de dados. Isso geralmente tem uma propriedade por tabela (por exemplo Customers
) e uma tabela é implementada IQueryable<Customer>
. Mas não usamos muito diretamente; considerar:
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);
}
}
isso se torna (pelo compilador C #):
var qry = ctx.Customers.Where(cust => cust.Region == "North")
.Select(cust => new { cust.Id, cust.Name });
que é novamente interpretado (pelo compilador C #) como:
var qry = Queryable.Select(
Queryable.Where(
ctx.Customers,
cust => cust.Region == "North"),
cust => new { cust.Id, cust.Name });
É importante ressaltar que os métodos estáticos nas Queryable
árvores de expressão, que - em vez da IL regular, são compilados em um modelo de objeto. Por exemplo - basta olhar para "Onde", isso nos dá algo comparável a:
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) ...
O compilador não fez muito por nós? Esse modelo de objeto pode ser desmembrado, inspecionado quanto ao significado e reunido novamente pelo gerador TSQL - fornecendo algo como:
SELECT c.Id, c.Name
FROM [dbo].[Customer] c
WHERE c.Region = 'North'
(a cadeia pode acabar como um parâmetro; não me lembro)
Nada disso seria possível se tivéssemos acabado de usar um delegado. E este é o ponto de Queryable
/ IQueryable<T>
: fornece o ponto de entrada para o uso de árvores de expressão.
Tudo isso é muito complexo, por isso é um bom trabalho que o compilador o torne agradável e fácil para nós.
Para obter mais informações, consulte " C # in Depth " ou " LINQ in Action ", ambos que fornecem cobertura desses tópicos.
Embora Reed Copsey e Marc Gravell já tenham descrito sobre IQueryable
(e também IEnumerable
) o suficiente, eu quero acrescentar um pouco mais aqui, fornecendo um pequeno exemplo IQueryable
e IEnumerable
o número de usuários solicitados.
Exemplo : Criei duas tabelas no banco de dados
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)
A chave primária ( PersonId
) da tabela Employee
também é uma chave forja ( personid
) da tabelaPerson
Em seguida, adicionei o modelo de entidade ado.net no meu aplicativo e criei a classe de serviço abaixo
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;
}
}
eles contêm o mesmo linq. Ele chamou program.cs
conforme definido abaixo
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();
}
}
A saída é a mesma para ambos, obviamente
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
Então a questão é qual / onde está a diferença? Não parece ter nenhuma diferença, certo? Realmente!!
Vamos dar uma olhada nas consultas sql geradas e executadas pelo framwork de entidade 5 durante esse período
Parte de execução 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]
Parte de execução 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 comum para ambas as partes de execução
/* 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
*/
Então, você tem algumas perguntas agora, deixe-me adivinhar e tentar respondê-las
Por que scripts diferentes são gerados para o mesmo resultado?
Vamos descobrir alguns pontos aqui,
todas as consultas têm uma parte comum
WHERE [Extent1].[PersonId] IN (0,1,2,3)
porque? Porque a função IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable
e
IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable
de SomeServiceClass
contém uma linha comum nas consultas linq
where employeesToCollect.Contains(e.PersonId)
Então, por que a
AND (N'M' = [Extent1].[Gender])
parte está faltando na IEnumerable
parte de execução, enquanto nas duas chamadas de funções usamos Where(i => i.Gender == "M") in
program.cs`
Agora estamos no ponto em que a diferença surgiu
IQueryable
eIEnumerable
O que o framwork de entidade faz quando um IQueryable
método é chamado, leva a instrução linq escrita dentro do método e tenta descobrir se mais expressões linq são definidas no conjunto de resultados e, em seguida, reúne todas as consultas linq definidas até que o resultado precise buscar e construa sql mais apropriado consulta para executar.
Fornece muitos benefícios, como
como aqui no exemplo, o sql server retornou ao aplicativo apenas duas linhas após a execução do IQueryable`, mas retornou TRÊS linhas para a consulta IEnumerable, por que?
No caso do IEnumerable
método, a estrutura da entidade pegou a instrução linq gravada dentro do método e constrói a consulta sql quando o resultado precisar buscar. ele não inclui resto linq parte para construir a consulta sql. Como aqui, nenhuma filtragem é feita no servidor sql na coluna gender
.
Mas as saídas são as mesmas? Como 'IEnumerable filtra o resultado ainda mais no nível do aplicativo após recuperar o resultado do servidor sql
Então, o que alguém deve escolher? Pessoalmente, prefiro definir o resultado da função, IQueryable<T>
pois, como existem muitos benefícios IEnumerable
, você pode associar duas ou mais funções IQueryable, que geram scripts mais específicos para o sql server.
Aqui, no exemplo, você pode ver que IQueryable Query(IQueryableQuery2)
gera um script mais específico do IEnumerable query(IEnumerableQuery2)
que é muito mais aceitável no meu ponto de vista.
Permite mais consultas mais abaixo na linha. Se isso estivesse além do limite de serviço, o usuário desse objeto IQueryable teria permissão para fazer mais com ele.
Por exemplo, se você estiver usando carregamento lento com nhibernate, isso poderá resultar no carregamento do gráfico quando / se necessário.