Há muito a dizer sobre isso. Deixe-me focar AsEnumerable
e AsQueryable
e menção ToList()
ao longo do caminho.
O que esses métodos fazem?
AsEnumerable
e AsQueryable
converter ou converter para IEnumerable
ou IQueryable
, respectivamente. Digo elenco ou converter com uma razão:
Quando o objeto de origem já implementa a interface de destino, o próprio objeto de origem é retornado, mas convertido na interface de destino. Em outras palavras: o tipo não é alterado, mas o tipo em tempo de compilação é.
Quando o objeto de origem não implementa a interface de destino, o objeto de origem é convertido em um objeto que implementa a interface de destino. Portanto, o tipo e o tipo de tempo de compilação são alterados.
Deixe-me mostrar isso com alguns exemplos. Eu tenho esse pequeno método que relata o tipo de tempo de compilação e o tipo real de um objeto ( cortesia de Jon Skeet ):
void ReportTypeProperties<T>(T obj)
{
Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}
Vamos tentar um linq-to-sql arbitrário Table<T>
, que implementa IQueryable
:
ReportTypeProperties(context.Observations);
ReportTypeProperties(context.Observations.AsEnumerable());
ReportTypeProperties(context.Observations.AsQueryable());
O resultado:
Compile-time type: Table`1
Actual type: Table`1
Compile-time type: IEnumerable`1
Actual type: Table`1
Compile-time type: IQueryable`1
Actual type: Table`1
Você vê que a própria classe da tabela sempre é retornada, mas sua representação é alterada.
Agora, um objeto que implementa IEnumerable
, não IQueryable
:
var ints = new[] { 1, 2 };
ReportTypeProperties(ints);
ReportTypeProperties(ints.AsEnumerable());
ReportTypeProperties(ints.AsQueryable());
Os resultados:
Compile-time type: Int32[]
Actual type: Int32[]
Compile-time type: IEnumerable`1
Actual type: Int32[]
Compile-time type: IQueryable`1
Actual type: EnumerableQuery`1
Aí está. AsQueryable()
converteu a matriz em um EnumerableQuery
, que "representa uma IEnumerable<T>
coleção como uma IQueryable<T>
fonte de dados". (MSDN).
Qual o uso?
AsEnumerable
é freqüentemente usado para alternar de qualquer IQueryable
implementação para LINQ para objetos (L2O), principalmente porque o primeiro não suporta funções que o L2O possui. Para obter mais detalhes, consulte Qual é o efeito de AsEnumerable () em uma entidade LINQ? .
Por exemplo, em uma consulta do Entity Framework, podemos usar apenas um número restrito de métodos. Portanto, se, por exemplo, precisarmos usar um de nossos próprios métodos em uma consulta, normalmente escreveremos algo como
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => MySuperSmartMethod(x))
ToList
- que converte um IEnumerable<T>
em List<T>
- é também frequentemente usado para esse fim. A vantagem de usar AsEnumerable
vs. ToList
é que AsEnumerable
não executa a consulta. AsEnumerable
preserva a execução adiada e não cria uma lista intermediária geralmente inútil.
Por outro lado, quando a execução forçada de uma consulta LINQ é desejada, ToList
pode ser uma maneira de fazer isso.
AsQueryable
pode ser usado para fazer uma coleção enumerável aceitar expressões em instruções LINQ. Veja aqui para mais detalhes: Eu realmente preciso usar AsQueryable () na coleção? .
Nota sobre abuso de substâncias!
AsEnumerable
funciona como uma droga. É uma solução rápida, mas com um custo e não soluciona o problema subjacente.
Em muitas respostas de estouro de pilha, vejo pessoas se candidatando AsEnumerable
para corrigir praticamente qualquer problema com métodos não suportados nas expressões LINQ. Mas o preço nem sempre é claro. Por exemplo, se você fizer isso:
context.MyLongWideTable // A table with many records and columns
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate })
... tudo está perfeitamente traduzido em uma instrução SQL que filtra ( Where
) e projeta ( Select
). Ou seja, o comprimento e a largura, respectivamente, do conjunto de resultados SQL são reduzidos.
Agora, suponha que os usuários desejem apenas ver a parte da data CreateDate
. No Entity Framework, você descobrirá rapidamente que ...
.Select(x => new { x.Name, x.CreateDate.Date })
... não é suportado (no momento da escrita). Felizmente, há a AsEnumerable
solução:
context.MyLongWideTable.AsEnumerable()
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate.Date })
Claro, corre, provavelmente. Mas ele puxa a tabela inteira para a memória e depois aplica o filtro e as projeções. Bem, a maioria das pessoas é inteligente o suficiente para fazer o Where
primeiro:
context.MyLongWideTable
.Where(x => x.Type == "type").AsEnumerable()
.Select(x => new { x.Name, x.CreateDate.Date })
Mas ainda todas as colunas são buscadas primeiro e a projeção é feita na memória.
A correção real é:
context.MyLongWideTable
.Where(x => x.Type == "type")
.Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) })
(Mas isso requer um pouco mais de conhecimento ...)
O que esses métodos NÃO fazem?
Restaurar recursos IQueryable
Agora, uma ressalva importante. Quando você faz
context.Observations.AsEnumerable()
.AsQueryable()
você terminará com o objeto de origem representado como IQueryable
. (Porque os dois métodos apenas convertem e não convertem).
Mas quando você faz
context.Observations.AsEnumerable().Select(x => x)
.AsQueryable()
qual será o resultado?
O Select
produz um WhereSelectEnumerableIterator
. Esta é uma classe .Net interna que implementa IEnumerable
, nãoIQueryable
. Portanto, ocorreu uma conversão para outro tipo e o subsequente AsQueryable
não pode mais retornar a fonte original.
A implicação disso é que o uso nãoAsQueryable
é uma maneira de injetar magicamente um provedor de consulta com seus recursos específicos em um enumerável. Suponha que você faça
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => x.ToString())
.AsQueryable()
.Where(...)
A condição where nunca será convertida em SQL. AsEnumerable()
seguido por instruções LINQ corta definitivamente a conexão com o provedor de consulta de estrutura de entidade.
Eu deliberadamente mostro este exemplo, porque já vi perguntas aqui onde pessoas, por exemplo, tentam 'injetar' Include
recursos em uma coleção chamando AsQueryable
. Ele compila e executa, mas não faz nada porque o objeto subjacente não possui mais uma Include
implementação.
Executar
Ambos AsQueryable
e AsEnumerable
não executam (ou enumeram ) o objeto de origem. Eles apenas alteram seu tipo ou representação. Ambas as interfaces envolvidas IQueryable
e IEnumerable
são nada mais que "uma enumeração esperando para acontecer". Eles não são executados antes de serem forçados a fazê-lo, por exemplo, como mencionado acima, chamando ToList()
.
Isso significa que executar um IEnumerable
obtido chamando AsEnumerable
um IQueryable
objeto executará o subjacente IQueryable
. Uma execução subsequente do IEnumerable
executará novamente o IQueryable
. O que pode ser muito caro.
Implementações específicas
Até agora, isso era apenas sobre os métodos de extensão Queryable.AsQueryable
e Enumerable.AsEnumerable
. Mas é claro que qualquer um pode escrever métodos de instância ou métodos de extensão com os mesmos nomes (e funções).
De fato, é um exemplo comum de um AsEnumerable
método de extensão específico DataTableExtensions.AsEnumerable
. DataTable
não implementa IQueryable
ou IEnumerable
, portanto, os métodos de extensão regulares não se aplicam.