Retornando IEnumerable <T> vs. IQueryable <T>

1085

Qual é a diferença entre retornar IQueryable<T>vs. IEnumerable<T>, quando um deve ser preferido em relação ao outro?

IQueryable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

IEnumerable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;
stackoverflowuser
fonte

Respostas:

1778

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 Takee Skipativar 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.

driis
fonte
32
Ótima explicação. Existem situações em que IEnumerable seria preferível a IQueryable?
Fjxx 23/02
8
Então, podemos dizer que, se estivermos usando o IQueryable para consultar o objeto de memória, eles não farão diferença entre IEnumerable e IQueryable?
Tarik
11
AVISO: Embora o IQueryable possa ser uma solução tentadora devido à otimização declarada, não deve ser permitido passar pelo repositório ou pela camada de serviço. Isso serve para proteger seu banco de dados contra a sobrecarga causada pelo "empilhamento de expressões LINQ".
Yorro
48
@fjxx Sim. Se você deseja filtrar repetidamente o resultado original (vários resultados finais). Fazer isso na interface IQueryable fará várias idas e voltas ao banco de dados, onde, como fazê-lo em IEnumerable vai fazer a filtragem na memória, tornando mais rápido (a menos que a quantidade de dados é enorme)
Per Hornshøj-Schierbeck
34
Outra razão para preferir IEnumerablea IQueryableé que nem todas as operações LINQ são suportados por todos os provedores LINQ. IQueryableDesde 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, IQueryableacabará 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 liberar IQueryables "no estado selvagem" após o repositório ou camada equivalente.
Avish 02/02
302

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 IEnumerableassinatura da versão é:Where(Func<Customer, bool> predicate)

  • A IQueryableassinatura 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 ambos IEnumerableeIQueryable

  • Ao usar Where()em uma IEnumerablecoleção, o compilador passa uma função compilada paraWhere()

  • Ao usar Where()em uma IQueryablecoleção, o compilador passa uma árvore de expressão para Where(). 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, IQueryablesó é útil se o provedor de dados subjacente puder fazer algo com ele. Convertendo algo como um regular Listpara IQueryablenão lhe trará nenhum benefício.

Jacob
fonte
9
OMI é melhor que a resposta aceita. No entanto, não entendo uma coisa: o IQueryable não oferece nenhum benefício para objetos regulares, OK, mas é pior de alguma forma? Como se isso não der nenhum benefício, não há motivo suficiente para preferir o IEnumerable; portanto, a idéia de usar o IQueryable em todo o lugar permanece válida.
Sergei Tachenov 7/08/16
1
Sergey, IQueryable estende IEnumerable, portanto, ao usar IQueryable, você carrega mais na memória do que uma instanciação IEnumerable faria! Então aqui está uma discussão. ( Stackoverflow.com/questions/12064828/... c ++ embora eu pensei que eu poderia extrapolar isso)
Viking
Concordando com Sergei sobre essa ser a melhor resposta (embora a resposta aceita seja boa). Eu gostaria de acrescentar que, na minha experiência, IQueryablefaz não funções de análise, bem como IEnumerablefaz: por exemplo, se você quer saber quais elementos de um DBSet<BookEntity>não está em um List<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()depois dbSetObject.
Jean-David Lanz
80

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:

MarketDevEntities db = new MarketDevEntities();

IEnumerable<WebLog> first = db.WebLogs;
var second = first.Where(c => c.DurationSeconds > 10);
var third = second.Where(c => c.WebLogID > 100);
var result = third.Where(c => c.EmailAddress.Length > 11);

Console.Write(result.First().UserName);

No SQL Server Profiler, encontramos um comando igual a:

"SELECT * FROM [dbo].[WebLog]"

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 IQueryablevez de IEnumerableno exemplo acima (segunda linha):

No SQL Server Profiler, encontramos um comando igual a:

"SELECT TOP 1 * FROM [dbo].[WebLog] WHERE [DurationSeconds] > 10 AND [WebLogID] > 100 AND LEN([EmailAddress]) > 11"

Demora aproximadamente quatro segundos para executar esse bloco de código usando IQueryable.

IQueryable possui uma propriedade chamada Expressionque armazena uma expressão em árvore que começa a ser criada quando usamos o resultexemplo (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.

Kasper Roma
fonte
5
Isso me ensina ao transmitir para IEnumerable, o IQueryable subjacente perde o método de extensão IQueryable.
Yiping
56

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 IEnumerableforçará automaticamente o tempo de execução a usar o LINQ to Objects para consultar sua coleção.

Retornar um IQueryable(que implementa IEnumerable, 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.).

Justin Niessner
fonte
30

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 IEnumerablese você deseja transportar um conjunto de objetos para enumerar.

Imagine IQueryablecomo é o que é - uma "consulta" para dados (que você pode refinar, se quiser). An IEnumerableé um conjunto de objetos (que já foram recebidos ou criados) sobre os quais você pode enumerar.

sebastianmehler
fonte
2
"Pode enumerar", não "IEnumerable".
Casey
28

Muito já foi dito anteriormente, mas de volta às raízes, de uma maneira mais técnica:

  1. 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 o foreachloop interno, embora você possa usar IEnumeratorapenas). Eles residem na memória como estão.
  2. 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.

IQueryablerepresenta 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 os IQueryableobjetos também são implementados IEnumerable, 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 IQueryableexpressões são convertidas em consultas T-SQL nativas . Nhibernatefaz 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 IQueryableobjetos 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 LINQrecurso 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, IEnumerableListas (indexadores e outros). Portanto, é meu conselho usar IQueryablesomente dentro de repositórios e IEnumerable em qualquer outro lugar do código. Não estou dizendo sobre as preocupações de testabilidade que IQueryablequebram 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.

Arman McHitarian
fonte
3
O comentário de que IEnumerables estão sempre na memória não é necessariamente necessário. A interface IQueryable implementa a interface IEnumerable. Por isso, é possível passar um IQueryable bruto que representa uma consulta LINQ-SQL diretamente em uma exibição que espera um IEnumerable! Você pode se surpreender ao descobrir que seu contexto de dados expirou ou que você acaba enfrentando problemas com o MARS (vários conjuntos de resultados ativos).
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. Mas eu realmente entendo o seu ponto. Vou adicionar um comentário sobre isso.
Arman McHitarian
@AlexanderPritchard, nenhum deles é 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 para que, se ela representa uma consulta, os resultados dessa consulta possam ser enumerados. A enumeração faz com que a árvore de expressão associada a um objeto IQueryable seja executada.
Arman McHitarian
24

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<>ou IEnumerable<>e você saberá que não está mudando o tipo.

Se você começar com um IQueryable<>, normalmente deseja mantê-lo como IQueryable<>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ê chamou Take(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<>para IEnumerable<>pode ser o fato de você estar chamando alguma função de extensão que a implementação de IQueryable<>em seu objeto específico não pode manipular ou manipular ineficientemente. Nesse caso, convém converter o tipo para IEnumerable<>(atribuindo a uma variável do tipo IEnumerable<>ou usando o AsEnumerablemétodo de extensão, por exemplo) para que as funções de extensão que você chama acabem sendo as da Enumerableclasse e não da Queryableclasse.

AJS
fonte
18

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

// Type: System.Linq.Enumerable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Enumerable
{
    public static IEnumerable<TSource> Where<TSource>(
        this IEnumerable<TSource> source, 
        Func<TSource, bool> predicate)
    {
        return (IEnumerable<TSource>) 
            new Enumerable.WhereEnumerableIterator<TSource>(source, predicate);
    }
}

e IQueryable<T>:

// Type: System.Linq.Queryable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(
        this IQueryable<TSource> source, 
        Expression<Func<TSource, bool>> predicate)
    {
        return source.Provider.CreateQuery<TSource>(
            Expression.Call(
                null, 
                ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(
                    new Type[] { typeof(TSource) }), 
                    new Expression[] 
                        { source.Expression, Expression.Quote(predicate) }));
    }
}

O primeiro retorna um iterador enumerável e o segundo cria uma consulta por meio do provedor de consultas, especificado na IQueryableorigem.

Olexander Ivanitskyi
fonte
11

Recentemente, tive um problema com o IEnumerablev IQueryable. O algoritmo usado primeiro executou uma IQueryableconsulta para obter um conjunto de resultados. Estes foram então passados ​​para um foreachloop, com os itens instanciados como uma classe Entity Framework (EF). Essa classe EF foi usada na fromcláusula de uma consulta Linq to Entity, causando o resultado IEnumerable.

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 IQueryableconsulta do Linq for Entities. A IEnumerablelevou 15 segundos eo IQueryablelevou 0,5 segundos para executar. Havia três tabelas envolvidas e, depois de ler isso, acredito que a IEnumerableconsulta 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.

sscheider
fonte
o motivo foi que as expressões IQueryable são convertidas em um SQL nativo no EF e são executadas diretamente no banco de dados, enquanto as listas IEnumerable são objetos na memória. Eles são buscados no banco de dados em algum momento quando você chama funções agregadas como Count, Sum ou qualquer Para ... e opera na memória posteriormente. IQueryables 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;)
Arman McHitarian
10

Gostaria de esclarecer algumas coisas devido a respostas aparentemente conflitantes (principalmente em torno de IEnumerable).

(1) IQueryableestende a IEnumerableinterface. (Você pode enviar um IQueryablepara algo que espera IEnumerablesem erros.)

(2) Ambos IQueryablee o IEnumerableLINQ 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, IEnumerablesnão são exclusivamente "in-memory". IQueryablesnem sempre são executados no banco de dados. IEnumerabledeve carregar as coisas na memória (uma vez recuperada, possivelmente preguiçosamente) porque não possui provedor de dados abstrato. IQueryablesconfie 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 IQueryabledo contexto EF. (Nenhum registro está na memória.)

(b) Passe IQueryablepara uma visualização cujo modelo é IEnumerable. (Válido. IQueryableEstende IEnumerable.)

(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 IEnumerabletentativas 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 IEnumerableno foreach( var record in resultSet )bloco e simultaneamente tentar acessar record.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

  • Descobri que a habilitação do MARS na cadeia de conexão funciona de maneira não confiável. Sugiro que você evite o MARS, a menos que seja bem compreendido e explicitamente desejado.

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 Includeentidades explicitamente relacionadas DbSet.


fonte
9

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

Tabish Usman
fonte
qual é otimizado e leve em termos de desempenho?
Sitecore Sam
@Sam "IQueryable" é mais preferido em termos de otimização e leveza.
Tabish Usman
6

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

user3710357
fonte
5

Além das 2 primeiras respostas realmente boas (de driis e de Jacob):

A interface IEnumerable está no espaço de nome System.Collections.

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.

A interface IQueryable está localizada no espaço para nome System.Linq.

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.

Gleb B
fonte
0

Além do acima, é interessante notar que você pode obter exceções se usar em IQueryablevez de IEnumerable:

O seguinte funciona bem se productsfor um IEnumerable:

products.Skip(-4);

No entanto, se productsfor um IQueryablee estiver tentando acessar registros de uma tabela de banco de dados, você receberá este erro:

O deslocamento especificado em uma cláusula OFFSET pode não ser negativo.

Isso ocorre porque a seguinte consulta foi construída:

SELECT [p].[ProductId]
FROM [Products] AS [p]
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS

e OFFSET não podem ter um valor negativo.

David Klempfner
fonte