Sim, ambos fornecerão execução adiada .
A diferença é que IQueryable<T>
é a interface que permite que o LINQ-SQL (LINQ.-to-nothing) realmente funcione. Portanto, se você refinar ainda mais sua consulta em uma IQueryable<T>
, essa consulta será executada no banco de dados, se possível.
Para o IEnumerable<T>
caso, será LINQ-to-object, o que significa que todos os objetos correspondentes à consulta original precisarão ser carregados na memória do banco de dados.
Em código:
IQueryable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);
Esse código executará o SQL para selecionar apenas clientes de ouro. O código a seguir, por outro lado, executará a consulta original no banco de dados, filtrando os clientes que não são de ouro na memória:
IEnumerable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);
Essa é uma diferença muito importante, e trabalhar IQueryable<T>
em muitos casos pode impedir que você retorne muitas linhas do banco de dados. Outro exemplo principal é a paginação: se você usar Take
e Skip
ativar IQueryable
, obterá apenas o número de linhas solicitadas; fazer isso em um IEnumerable<T>
fará com que todas as suas linhas sejam carregadas na memória.
IEnumerable
aIQueryable
é que nem todas as operações LINQ são suportados por todos os provedores LINQ.IQueryable
Desde que você saiba o que está fazendo, pode usar para enviar o máximo de consultas ao provedor LINQ (LINQ2SQL, EF, NHibernate, MongoDB etc.). Mas se você deixar outro código fazer o que quiser com o seu,IQueryable
acabará tendo problemas porque algum código do cliente em algum lugar usou uma operação não suportada. Concordo com a recomendação de não liberarIQueryable
s "no estado selvagem" após o repositório ou camada equivalente.A resposta principal é boa, mas não menciona árvores de expressão que explicam "como" as duas interfaces diferem. Basicamente, existem dois conjuntos idênticos de extensões LINQ.
Where()
,Sum()
,Count()
,FirstOrDefault()
, Etc todos têm duas versões: uma que aceita funções e um que aceita expressões.A
IEnumerable
assinatura da versão é:Where(Func<Customer, bool> predicate)
A
IQueryable
assinatura da versão é:Where(Expression<Func<Customer, bool>> predicate)
Você provavelmente já usou os dois sem perceber, porque os dois são chamados usando uma sintaxe idêntica:
por exemplo,
Where(x => x.City == "<City>")
trabalha em ambosIEnumerable
eIQueryable
Ao usar
Where()
em umaIEnumerable
coleção, o compilador passa uma função compilada paraWhere()
Ao usar
Where()
em umaIQueryable
coleção, o compilador passa uma árvore de expressão paraWhere()
. Uma árvore de expressão é como o sistema de reflexão, mas para o código. O compilador converte seu código em uma estrutura de dados que descreve o que seu código faz em um formato facilmente digerível.Por que se preocupar com essa coisa de árvore de expressão? Eu só quero
Where()
filtrar meus dados. O principal motivo é que os ORMs EF e Linq2SQL podem converter árvores de expressão diretamente em SQL, onde seu código será executado muito mais rapidamente.Ah, isso soa como um aumento de desempenho gratuito, devo usar em
AsQueryable()
todo o lugar nesse caso? Não,IQueryable
só é útil se o provedor de dados subjacente puder fazer algo com ele. Convertendo algo como um regularList
paraIQueryable
não lhe trará nenhum benefício.fonte
IQueryable
faz não funções de análise, bem comoIEnumerable
faz: por exemplo, se você quer saber quais elementos de umDBSet<BookEntity>
não está em umList<BookObject>
,dbSetObject.Where(e => !listObject.Any(o => o.bookEntitySource == e))
lança uma exceção:Expression of type 'BookEntity' cannot be used for parameter of type 'BookObject' of method 'Boolean Contains[BookObject] (IEnumerable[BookObject], BookObject)'
. Eu tive que adicionar.ToList()
depoisdbSetObject
.Sim, ambos usam execução adiada. Vamos ilustrar a diferença usando o criador de perfil do SQL Server ....
Quando executamos o seguinte código:
No SQL Server Profiler, encontramos um comando igual a:
Demora aproximadamente 90 segundos para executar esse bloco de código em uma tabela WebLog que possui 1 milhão de registros.
Portanto, todos os registros da tabela são carregados na memória como objetos e, a cada .Where (), haverá outro filtro na memória para esses objetos.
Quando usamos em
IQueryable
vez deIEnumerable
no exemplo acima (segunda linha):No SQL Server Profiler, encontramos um comando igual a:
Demora aproximadamente quatro segundos para executar esse bloco de código usando
IQueryable
.IQueryable possui uma propriedade chamada
Expression
que armazena uma expressão em árvore que começa a ser criada quando usamos oresult
exemplo (que é chamado de execução adiada) e, no final, essa expressão será convertida em uma consulta SQL para executar no mecanismo de banco de dados.fonte
Ambos darão execução adiada, sim.
Quanto ao que é preferido em relação ao outro, depende de qual é a sua fonte de dados subjacente.
Retornar um
IEnumerable
forçará automaticamente o tempo de execução a usar o LINQ to Objects para consultar sua coleção.Retornar um
IQueryable
(que implementaIEnumerable
, a propósito) fornece a funcionalidade extra para converter sua consulta em algo que pode ter um desempenho melhor na origem subjacente (LINQ to SQL, LINQ to XML, etc.).fonte
Em termos gerais, eu recomendaria o seguinte:
Retorne
IQueryable<T>
se desejar ativar o desenvolvedor usando seu método para refinar a consulta retornada antes da execução.Retorne
IEnumerable
se você deseja transportar um conjunto de objetos para enumerar.Imagine
IQueryable
como é o que é - uma "consulta" para dados (que você pode refinar, se quiser). AnIEnumerable
é um conjunto de objetos (que já foram recebidos ou criados) sobre os quais você pode enumerar.fonte
Muito já foi dito anteriormente, mas de volta às raízes, de uma maneira mais técnica:
IEnumerable
é uma coleção de objetos na memória que você pode enumerar - uma sequência na memória que possibilita a iteração (facilita oforeach
loop interno, embora você possa usarIEnumerator
apenas). Eles residem na memória como estão.IQueryable
é uma árvore de expressão que será traduzida em outra coisa em algum momento com capacidade de enumerar o resultado final . Eu acho que é isso que confunde a maioria das pessoas.Eles obviamente têm conotações diferentes.
IQueryable
representa uma árvore de expressão (uma consulta, simplesmente) que será traduzida para outra coisa pelo provedor de consulta subjacente assim que as APIs de liberação forem chamadas, como funções agregadas LINQ (Sum, Count, etc.) ou ToList [Array, Dictionary ,. ..] E osIQueryable
objetos também são implementadosIEnumerable
,IEnumerable<T>
para que, se representarem uma consulta, o resultado dessa consulta possa ser iterado. Isso significa que o IQueryable não precisa ser apenas consultas. O termo certo é que são árvores de expressão .Agora, como essas expressões são executadas e o que elas recorrem depende dos chamados provedores de consulta (executores de expressão em que podemos pensar).
No mundo do Entity Framework (que é o provedor de origem de dados subjacente místico, ou o provedor de consultas), as
IQueryable
expressões são convertidas em consultas T-SQL nativas .Nhibernate
faz coisas semelhantes com eles. Você pode escrever o seu próprio seguindo os conceitos descritos em LINQ: Construindo um link IQueryable Provider , por exemplo, e você pode querer ter uma API de consulta personalizada para o serviço de provedor de loja de produtos.Então, basicamente, os
IQueryable
objetos estão sendo construídos até o momento em que os liberamos explicitamente e dizemos ao sistema para reescrevê-los no SQL ou qualquer outra coisa e enviar a cadeia de execução para processamento posterior.Como para adiar a execução, é um
LINQ
recurso para manter o esquema da árvore de expressão na memória e enviá-lo para a execução somente sob demanda, sempre que determinadas APIs são chamadas na sequência (a mesma contagem, lista de tarefas etc.).O uso adequado de ambos depende muito das tarefas que você está enfrentando para o caso específico. Para o conhecido padrão de repositório, eu pessoalmente opto por retornar
IList
, ou seja,IEnumerable
Listas (indexadores e outros). Portanto, é meu conselho usarIQueryable
somente dentro de repositórios e IEnumerable em qualquer outro lugar do código. Não estou dizendo sobre as preocupações de testabilidade queIQueryable
quebram e arruinam o princípio da separação de preocupações . Se você retornar uma expressão de dentro dos repositórios, os consumidores poderão brincar com a camada de persistência como desejariam.Um pequeno acréscimo à bagunça :) (de uma discussão nos comentários)) Nenhum deles é um objeto na memória, pois não é um tipo real em si, é um tipo de marcador - se você quiser ir tão fundo. Mas faz sentido (e é por isso que até o MSDN as coloca dessa maneira) pensar em IEnumerables como coleções na memória, enquanto IQueryables como árvores de expressão. O ponto é que a interface IQueryable herda a interface IEnumerable, de modo que, se representa uma consulta, os resultados dessa consulta podem ser enumerados. A enumeração faz com que a árvore de expressão associada a um objeto IQueryable seja executada. Portanto, na verdade, você não pode realmente chamar nenhum membro IEnumerable sem ter o objeto na memória. Vai chegar lá se você, de qualquer maneira, se não estiver vazio. IQueryables são apenas consultas, não os dados.
fonte
Em geral, você deseja preservar o tipo estático original da consulta até que isso importe.
Por esse motivo, você pode definir sua variável como 'var' em vez de ou
IQueryable<>
ouIEnumerable<>
e você saberá que não está mudando o tipo.Se você começar com um
IQueryable<>
, normalmente deseja mantê-lo comoIQueryable<>
até que haja algum motivo convincente para alterá-lo. A razão para isso é que você deseja fornecer ao processador de consultas o máximo de informações possível. Por exemplo, se você usar apenas 10 resultados (que você chamouTake(10)
), deseja que o SQL Server saiba disso para que ele possa otimizar seus planos de consulta e enviar apenas os dados que você usará.Um motivo convincente para alterar o tipo de
IQueryable<>
paraIEnumerable<>
pode ser o fato de você estar chamando alguma função de extensão que a implementação deIQueryable<>
em seu objeto específico não pode manipular ou manipular ineficientemente. Nesse caso, convém converter o tipo paraIEnumerable<>
(atribuindo a uma variável do tipoIEnumerable<>
ou usando oAsEnumerable
método de extensão, por exemplo) para que as funções de extensão que você chama acabem sendo as daEnumerable
classe e não daQueryable
classe.fonte
Há uma postagem no blog com um breve exemplo de código-fonte sobre como o uso indevido
IEnumerable<T>
pode afetar drasticamente o desempenho da consulta LINQ: Estrutura de entidade: IQueryable vs. IEnumerable .Se aprofundarmos e examinarmos as fontes, podemos ver que obviamente existem diferentes métodos de extensão para
IEnumerable<T>
:e
IQueryable<T>
:O primeiro retorna um iterador enumerável e o segundo cria uma consulta por meio do provedor de consultas, especificado na
IQueryable
origem.fonte
Recentemente, tive um problema com o
IEnumerable
vIQueryable
. O algoritmo usado primeiro executou umaIQueryable
consulta para obter um conjunto de resultados. Estes foram então passados para umforeach
loop, com os itens instanciados como uma classe Entity Framework (EF). Essa classe EF foi usada nafrom
cláusula de uma consulta Linq to Entity, causando o resultadoIEnumerable
.Sou bastante novo na EF e no Linq for Entities, então demorou um pouco para descobrir qual era o gargalo. Usando o MiniProfiling, encontrei a consulta e converti todas as operações individuais em uma única
IQueryable
consulta do Linq for Entities. AIEnumerable
levou 15 segundos eoIQueryable
levou 0,5 segundos para executar. Havia três tabelas envolvidas e, depois de ler isso, acredito que aIEnumerable
consulta estava realmente formando um produto cruzado de três tabelas e filtrando os resultados.Tente usar o IQueryables como regra geral e analise seu trabalho para tornar suas alterações mensuráveis.
fonte
IQueryable
s também ficam presos na memória depois que você chama uma dessas APIs, mas, se não, pode passar a expressão para a pilha das camadas e brincar com os filtros até a chamada da API. DAL bem concebido como um bom projetado Repository vai resolver este tipo de problemas;)Gostaria de esclarecer algumas coisas devido a respostas aparentemente conflitantes (principalmente em torno de IEnumerable).
(1)
IQueryable
estende aIEnumerable
interface. (Você pode enviar umIQueryable
para algo que esperaIEnumerable
sem erros.)(2) Ambos
IQueryable
e oIEnumerable
LINQ tentam carregar preguiçosamente ao iterar sobre o conjunto de resultados. (Observe que a implementação pode ser vista nos métodos de extensão de interface para cada tipo.)Em outras palavras,
IEnumerables
não são exclusivamente "in-memory".IQueryables
nem sempre são executados no banco de dados.IEnumerable
deve carregar as coisas na memória (uma vez recuperada, possivelmente preguiçosamente) porque não possui provedor de dados abstrato.IQueryables
confie em um provedor abstrato (como LINQ-to-SQL), embora também possa ser o provedor de memória .NET.Exemplo de caso de uso
(a) Recupere a lista de registros a partir
IQueryable
do contexto EF. (Nenhum registro está na memória.)(b) Passe
IQueryable
para uma visualização cujo modelo éIEnumerable
. (Válido.IQueryable
EstendeIEnumerable
.)(c) Itere e acesse os registros, entidades filhas e propriedades do conjunto de dados a partir da visualização. (Pode causar exceções!)
Possíveis Questões
(1) As
IEnumerable
tentativas de carregamento lento e seu contexto de dados expiraram. Exceção lançada porque o provedor não está mais disponível.(2) Os proxies de entidade do Entity Framework estão ativados (o padrão) e você tenta acessar um objeto relacionado (virtual) com um contexto de dados expirados. O mesmo que (1).
(3) Vários conjuntos de resultados ativos (MARS). Se você estiver iterando
IEnumerable
noforeach( var record in resultSet )
bloco e simultaneamente tentar acessarrecord.childEntity.childProperty
, poderá acabar com o MARS devido ao carregamento lento do conjunto de dados e da entidade relacional. Isso causará uma exceção se não estiver ativada na cadeia de conexão.Solução
Execute a consulta e armazene os resultados chamando
resultList = resultSet.ToList()
Esta parece ser a maneira mais direta de garantir que suas entidades estejam na memória.Nos casos em que você está acessando entidades relacionadas, você ainda pode exigir um contexto de dados. Ou você pode desativar os proxies de entidade e
Include
entidades explicitamente relacionadasDbSet
.fonte
A principal diferença entre "IEnumerable" e "IQueryable" é sobre onde a lógica do filtro é executada. Um é executado no lado do cliente (na memória) e o outro é executado no banco de dados.
Por exemplo, podemos considerar um exemplo em que temos 10.000 registros para um usuário em nosso banco de dados e digamos que apenas 900 são usuários ativos. Nesse caso, se usarmos “IEnumerable”, primeiro ele carregará todos os 10.000 registros na memória e aplica o filtro IsActive nele, que eventualmente retorna os 900 usuários ativos.
Enquanto, por outro lado, no mesmo caso, se usarmos "IQueryable", ele aplicará diretamente o filtro IsActive no banco de dados, que diretamente dali retornará os 900 usuários ativos.
Link de referência
fonte
Podemos usar os dois da mesma maneira, e eles são apenas diferentes no desempenho.
IQueryable é executado apenas no banco de dados de maneira eficiente. Isso significa que ele cria uma consulta de seleção inteira e obtém apenas os registros relacionados.
Por exemplo, queremos levar os 10 principais clientes cujo nome começa com 'Nimal'. Nesse caso, a consulta de seleção será gerada como
select top 10 * from Customer where name like ‘Nimal%’
.Mas se usamos IEnumerable, a consulta seria semelhante
select * from Customer where name like ‘Nimal%’
e as dez principais serão filtradas no nível de codificação C # (ela obtém todos os registros do cliente do banco de dados e os passa para C #).fonte
Além das 2 primeiras respostas realmente boas (de driis e de Jacob):
O objeto IEnumerable representa um conjunto de dados na memória e pode mover esses dados apenas para frente. A consulta representada pelo objeto IEnumerable é executada imediata e completamente, para que o aplicativo receba dados rapidamente.
Quando a consulta é executada, IEnumerable carrega todos os dados e, se precisarmos filtrá-los, a própria filtragem é feita no lado do cliente.
O objeto IQueryable fornece acesso remoto ao banco de dados e permite navegar pelos dados em uma ordem direta do começo ao fim ou na ordem inversa. No processo de criação de uma consulta, o objeto retornado é IQueryable, a consulta é otimizada. Como resultado, menos memória é consumida durante sua execução, menos largura de banda da rede, mas, ao mesmo tempo, pode ser processada um pouco mais lentamente do que uma consulta que retorna um objeto IEnumerable.
O que escolher?
Se você precisar de todo o conjunto de dados retornados, é melhor usar o IEnumerable, que fornece a velocidade máxima.
Se você NÃO precisar de todo o conjunto de dados retornados, mas apenas de alguns dados filtrados, é melhor usar o IQueryable.
fonte
Além do acima, é interessante notar que você pode obter exceções se usar em
IQueryable
vez deIEnumerable
:O seguinte funciona bem se
products
for umIEnumerable
:No entanto, se
products
for umIQueryable
e estiver tentando acessar registros de uma tabela de banco de dados, você receberá este erro:Isso ocorre porque a seguinte consulta foi construída:
e OFFSET não podem ter um valor negativo.
fonte