Noções básicas sobre .AsEnumerable() em LINQ to SQL
-
27-09-2020 - |
Pergunta
Dada a seguinte consulta LINQ to SQL:
var test = from i in Imports
where i.IsActive
select i;
A instrução SQL interpretada é:
SELECT [t0].[id] AS [Id] .... FROM [Imports] AS [t0] WHERE [t0].[isActive] = 1
Digamos que eu queira realizar alguma ação no select que não pode ser convertida para SQL.Entendo que a maneira convencional de conseguir isso é fazer AsEnumerable()
convertendo-o assim em um objeto viável.
Dado este código atualizado:
var test = from i in Imports.AsEnumerable()
where i.IsActive
select new
{
// Make some method call
};
E SQL atualizado:
SELECT [t0].[id] AS [Id] ... FROM [Imports] AS [t0]
Observe a falta de uma cláusula where na instrução SQL executada.
Isso significa que toda a tabela "Importações" está armazenada em cache na memória?Esse desempenho seria lento se a tabela contivesse uma grande quantidade de registros?
Ajude-me a entender o que realmente está acontecendo nos bastidores aqui.
Solução
A razão para AsEnumerable é para
Atenumerable (Tsource) (ienumerable (tsource)) pode ser usado para escolher entre implementações de consulta quando uma sequência implementa ienumerable (t), mas também possui um conjunto diferente de métodos de consulta pública disponíveis disponíveis
Então, quando você estava ligando para o Where
método antes, você estava chamando um método diferente Where
método do IEnumerable.Where
.Que Where
declaração era para o LINQ converter para SQL, o novo Where
é o IEnumerable
aquele que leva um IEnumerable
, enumera-o e produz os itens correspondentes.O que explica por que você vê diferentes SQL sendo gerados.A tabela será retirada integralmente do banco de dados antes do Where
extensão será aplicada em sua segunda versão do código.Isso poderia criar um sério gargalo, porque toda a tabela teria que estar na memória, ou pior, toda a tabela teria que viajar entre servidores.Permitir que o SQL Server execute o Where
e faça o que faz de melhor.
Outras dicas
No ponto em que a enumeração é enumerada, o banco de dados será consultado e todo o conjunto de resultados será recuperado.
Uma solução parcial pode ser o caminho.Considerar
var res = (
from result in SomeSource
where DatabaseConvertableCriterion(result)
&& NonDatabaseConvertableCriterion(result)
select new {result.A, result.B}
);
Digamos também que NonDatabaseConvertableCriterion requer o campo C do resultado.Como NonDatabaseConvertableCriterion faz o que seu nome sugere, isso deve ser executado como uma enumeração.No entanto, considere:
var partWay =
(
from result in SomeSource
where DatabaseConvertableCriterion(result)
select new {result.A, result.B, result.C}
);
var res =
(
from result in partWay.AsEnumerable()
where NonDatabaseConvertableCriterion select new {result.A, result.B}
);
Neste caso, quando res é enumerado, consultado ou utilizado de outra forma, tanto trabalho quanto possível será passado para o banco de dados, que retornará o suficiente para continuar o trabalho.Supondo que seja realmente impossível reescrever para que todo o trabalho possa ser enviado para o banco de dados, este pode ser um compromisso adequado.
Existem três implementações de AsEnumerable
.
DataTableExtensions.AsEnumerable
Estende um DataTable
para dar-lhe um IEnumerable
interface para que você possa usar o Linq contra o DataTable
.
Enumerable.AsEnumerable<TSource>
e ParallelEnumerable.AsEnumerable<TSource>
O
AsEnumerable<TSource>(IEnumerable<TSource>)
O método não tem efeito além de alterar o tipo de fonte do tempo de compilação de um tipo que implementaIEnumerable<T>
paraIEnumerable<T>
em si.
AsEnumerable<TSource>(IEnumerable<TSource>)
pode ser usado para escolher entre implementações de consulta quando uma sequência implementaIEnumerable<T>
mas também possui um conjunto diferente de métodos de consulta pública disponíveis.Por exemplo, dada uma classe genéricaTable
que implementaIEnumerable<T>
e tem seus próprios métodos, comoWhere
,Select
, eSelectMany
, uma chamada paraWhere
invocaria o públicoWhere
método deTable
.ATable
tipo que representa uma tabela de banco de dados pode ter umWhere
Método que toma o argumento do predicado como uma árvore de expressão e converte a árvore em SQL para execução remota.Se a execução remota não for desejada, por exemplo, porque o predicado invoca um método local, oAsEnumerable<TSource>
O método pode ser usado para ocultar os métodos personalizados e, em vez disso, disponibilizar os operadores de consulta padrão.
Em outras palavras.
Se eu tiver um
IQueryable<X> sequence = ...;
de um LinqProvider, como Entity Framework, e eu,
sequence.Where(x => SomeUnusualPredicate(x));
essa consulta será composta e executada no servidor.Isso falhará em tempo de execução porque o EntityFramework não sabe como converter SomeUnusualPredicate
em SQL.
Se eu quiser executar a instrução com Linq to Objects, eu faço,
sequence.AsEnumerable().Where(x => SomeUnusualPredicate(x));
agora o servidor retornará todos os dados e o Enumerable.Where
do Linq para Objects será usado em vez da implementação do Query Provider.
Não importa que o Entity Framework não saiba interpretar SomeUnusualPredicate
, minha função será usada diretamente.(No entanto, esta pode ser uma abordagem ineficiente, uma vez que todas as linhas serão retornadas do servidor.)
Acredito que o AsEnumerable apenas informa ao compilador quais métodos de extensão usar (neste caso, os definidos para IEnumerable em vez daqueles para IQueryable).A execução da consulta ainda é adiada até que você chame ToArray ou enumere nela.