IEnumerable x IQueryable | CSharp
- IEnumerable x IQueryable | CSharp
Tabelas explicativas
| Característica | IQueryable<T> | IEnumerable<T> | IAsyncCursor<T> |
|---|---|---|---|
| Onde a query executa | No banco (MongoDB) | Na memória da aplicação | No banco (streaming) |
| Quando os dados são carregados | Apenas quando executa (ToList, First, etc.) | Já estão carregados | Em lotes conforme percorre |
| Tradução de LINQ | Sim, traduz para query Mongo | Não | Não usa LINQ diretamente |
| Performance em grandes volumes | Boa (se usado corretamente) | Ruim (carrega tudo na memória) | Excelente |
| Uso típico | Filtros e consultas com LINQ | Processamento de dados já carregados | Processar muitos registros |
| Execução | Deferred (adiada) | Imediata após carregar | Iterativa |
| Risco comum | LINQ não suportado pelo driver | Estourar memória | Código mais verboso |
| Exemplo | collection.AsQueryable() | list.Where() | collection.FindAsync() |
| Tipo | Descrição | ❌ Exemplo errado | ✅ Exemplo correto |
|---|---|---|---|
IQueryable<T> | Query é traduzida para o MongoDB e executada no banco | csharp\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ória | csharp\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 banco | csharp\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óriaIQueryable→ 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:
IQueryableherda deIEnumerable- Todo
IQueryablepode ser tratado comoIEnumerable - 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ística | IEnumerable | IQueryable |
|---|---|---|
| Execução | Memória | Fonte de dados |
| Tradução para SQL | Não | Sim |
| Performance em grandes volumes | Baixa | Alta |
| Uso de memória | Alto | Baixo |
| Flexibilidade com métodos .NET | Alta | Limitada |
| Uso comum | Coleções locais | Banco 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