Skip to main content

IEnumerable x IQueryable | CSharp

  • IEnumerable x IQueryable | CSharp

Tabelas explicativas

CaracterísticaIQueryable<T>IEnumerable<T>IAsyncCursor<T>
Onde a query executaNo banco (MongoDB)Na memória da aplicaçãoNo banco (streaming)
Quando os dados são carregadosApenas quando executa (ToList, First, etc.)Já estão carregadosEm lotes conforme percorre
Tradução de LINQSim, traduz para query MongoNãoNão usa LINQ diretamente
Performance em grandes volumesBoa (se usado corretamente)Ruim (carrega tudo na memória)Excelente
Uso típicoFiltros e consultas com LINQProcessamento de dados já carregadosProcessar muitos registros
ExecuçãoDeferred (adiada)Imediata após carregarIterativa
Risco comumLINQ não suportado pelo driverEstourar memóriaCódigo mais verboso
Exemplocollection.AsQueryable()list.Where()collection.FindAsync()

TipoDescrição❌ Exemplo errado✅ Exemplo correto
IQueryable<T>Query é traduzida para o MongoDB e executada no bancocsharp\nvar users = collection.AsQueryable().ToList();\nusers = users.Where(u => u.Age > 18);\n (filtro acontece na memória)csharp\nvar users = collection.AsQueryable()\n .Where(u => u.Age > 18)\n .ToList();\n (filtro executa no banco)
IEnumerable<T>Trabalha com dados já carregados na memóriacsharp\nvar users = collection.Find(_ => true)\n .ToList()\n .Where(u => u.Age > 18);\n (carrega tudo antes de filtrar)csharp\nvar users = collection\n .Find(u => u.Age > 18)\n .ToList();\n (filtro feito no banco)
IAsyncCursor<T>Cursor que lê dados em lotes do bancocsharp\nvar cursor = await collection.FindAsync(_ => true);\nvar list = await cursor.ToListAsync();\nlist.ForEach(u => Console.WriteLine(u.Name));\n (perde vantagem de streaming)csharp\nvar cursor = await collection.FindAsync(_ => true);\nawait cursor.ForEachAsync(u =>\n{\n Console.WriteLine(u.Name);\n});\n (processa em lotes)

Visão Geral

No ecossistema do C# e LINQ, duas interfaces são frequentemente utilizadas para trabalhar com coleções de dados:

  • IEnumerable<T>
  • IQueryable<T>

Ambas permitem executar consultas utilizando LINQ, porém possuem comportamentos muito diferentes quanto à execução da consulta, local de processamento e performance.

A principal diferença entre elas está em onde a consulta é executada:

  • IEnumerable → consulta executada em memória
  • IQueryable → consulta traduzida para consulta no provedor de dados (ex: SQL no banco)

Isso impacta diretamente performance, uso de memória e volume de dados trafegados.


IEnumerable

IEnumerable<T> representa uma coleção enumerável em memória.

Ele faz parte do namespace System.Collections e é a base para percorrer coleções utilizando foreach.

Quando utilizado com LINQ, a consulta é executada no lado da aplicação, ou seja, depois que os dados já foram carregados da fonte de dados.

Exemplo

IEnumerable<Cliente> clientes = Context.Cliente.ToList();

var resultado = clientes.Where(c => c.CodigoCliente == 3);

SQL gerado

SELECT * FROM Cliente

Neste caso:

  • Todos os registros são carregados
  • O filtro Where é executado na memória da aplicação

Fluxo de execução

Banco de dados

SELECT * FROM Cliente

Todos os dados vão para memória

LINQ filtra na aplicação

IQueryable

IQueryable<T> representa uma coleção consultável que permite construir expressões de consulta que serão traduzidas pelo provedor de dados.

Ele pertence ao namespace System.Linq e é amplamente utilizado por ORMs, como:

  • Entity Framework
  • EF Core
  • NHibernate

A consulta é transformada em árvore de expressão (Expression Tree) e depois convertida para o formato da fonte de dados (ex: SQL).

Exemplo

IQueryable<Cliente> clientes = Context.Cliente;

var resultado = clientes.Where(c => c.CodigoCliente == 3);

SQL gerado

SELECT *
FROM Cliente
WHERE CodigoCliente = 3

Neste caso:

  • O filtro é executado diretamente no banco
  • Apenas os dados necessários são retornados

Fluxo de execução

Aplicação monta expressão LINQ

Provedor traduz para SQL

Banco executa a consulta

Somente os dados filtrados retornam

Diferença de Execução

IEnumerable

var clientes = Context.Cliente.ToList(); // executa query

var resultado = clientes.Where(c => c.CodigoCliente == 3);

Execução:

  • Query executada primeiro
  • Filtro executado depois em memória

IQueryable

var clientes = Context.Cliente
.Where(c => c.CodigoCliente == 3);

Execução:

  • A query só é executada quando ocorre materialização:
clientes.ToList();
clientes.First();
clientes.Count();

Deferred Execution (Execução Tardia)

Tanto IEnumerable quanto IQueryable utilizam Deferred Execution.

Ou seja, a consulta não é executada imediatamente, apenas quando ocorre materialização.

Exemplo

var query = Context.Cliente
.Where(c => c.Ativo);

query = query.Where(c => c.Idade > 18);
query = query.OrderBy(c => c.Nome);

var resultado = query.ToList();

SQL final gerado:

SELECT *
FROM Cliente
WHERE Ativo = 1
AND Idade > 18
ORDER BY Nome

Herança entre as Interfaces

A relação entre elas é:

IEnumerable<T>

IQueryable<T>

Ou seja:

  • IQueryable herda de IEnumerable
  • Todo IQueryable pode ser tratado como IEnumerable
  • Mas o contrário não é verdadeiro

Conversão entre IQueryable e IEnumerable

Convertendo para IEnumerable

var clientes = Context.Cliente
.Where(c => c.Ativo)
.AsEnumerable();

A partir deste ponto:

  • qualquer operação LINQ será executada em memória

Convertendo para IQueryable

var lista = new List<Cliente>();

var query = lista.AsQueryable();

Neste caso:

  • ainda é executado em memória
  • pois a fonte não possui provedor de tradução (como SQL)

Pontos Positivos do IEnumerable

  • Simplicidade de uso
  • Ideal para coleções em memória
  • Melhor para manipulação de dados já carregados
  • Evita dependência de provedor externo
  • Útil para processamento de dados locais

Exemplos ideais

  • List
  • Array
  • Dados já materializados
  • Transformações complexas em memória
var clientes = listaClientes
.Where(c => c.Idade > 18)
.Select(c => c.Nome);

Pontos Negativos do IEnumerable

  • Pode causar grande consumo de memória
  • Pode carregar dados desnecessários
  • Não aproveita otimização do banco
  • Pode gerar problemas de performance com grandes volumes

Exemplo problemático

var clientes = Context.Cliente.ToList();

var ativos = clientes.Where(c => c.Ativo);

Se a tabela tiver 1 milhão de registros:

  • todos serão carregados para memória
  • o filtro ocorrerá depois

Pontos Positivos do IQueryable

  • Melhor performance em consultas grandes
  • Filtragem feita diretamente no banco
  • Menor tráfego de dados
  • Permite composição dinâmica de queries
  • Ideal para APIs e repositórios

Exemplo

IQueryable<Cliente> query = Context.Cliente;

if (filtro.Ativo)
query = query.Where(c => c.Ativo);

if (filtro.IdadeMinima > 0)
query = query.Where(c => c.Idade >= filtro.IdadeMinima);

var resultado = query.ToList();

SQL gerado somente com filtros necessários.


Pontos Negativos do IQueryable

  • Pode gerar queries SQL complexas
  • Depende do provedor (EF, EF Core, etc)
  • Nem todos os métodos .NET podem ser traduzidos para SQL
  • Debug pode ser mais difícil

Exemplo que pode falhar

var clientes = Context.Cliente
.Where(c => MetodoPersonalizado(c.Nome));

Erro comum:

The LINQ expression could not be translated

Porque o método não pode ser convertido para SQL.


Comparação Direta

CaracterísticaIEnumerableIQueryable
ExecuçãoMemóriaFonte de dados
Tradução para SQLNãoSim
Performance em grandes volumesBaixaAlta
Uso de memóriaAltoBaixo
Flexibilidade com métodos .NETAltaLimitada
Uso comumColeções locaisBanco de dados

Boas Práticas

Prefira IQueryable quando

  • Trabalhar com Entity Framework
  • Consultar banco de dados
  • Manipular grandes volumes
  • Construir queries dinâmicas

Prefira IEnumerable quando

  • Os dados já estão em memória
  • Realizar processamentos complexos
  • Executar lógica que não pode virar SQL
  • Trabalhar com coleções locais

Exemplo Comparativo Completo

Uso incorreto (IEnumerable)

var clientes = Context.Cliente.ToList();

var resultado = clientes
.Where(c => c.Idade > 18)
.Take(10);

SQL executado:

SELECT * FROM Cliente

Problema:

  • todos os dados são carregados

Uso correto (IQueryable)

var clientes = Context.Cliente
.Where(c => c.Idade > 18)
.Take(10)
.ToList();

SQL gerado:

SELECT TOP(10) *
FROM Cliente
WHERE Idade > 18

Benefício:

  • banco faz todo o trabalho
  • aplicação recebe apenas os dados necessários