Nuestro equipo redactor ha estado mucho tiempo buscando para darle soluciones a tus dudas, te compartimos la respuesta así que esperamos resultarte de mucha apoyo.
Solución:
Hay mucho que decir al respecto. Déjame concentrarme en AsEnumerable
y AsQueryable
y mencionar ToList()
por el camino.
¿Qué hacen estos métodos?
AsEnumerable
y AsQueryable
emitir o convertir a IEnumerable
o IQueryable
, respectivamente. yo digo emitir o convertir con una razón:
-
Cuando el objeto de origen ya implementa la interfaz de destino, el objeto de origen en sí se devuelve pero emitir a la interfaz de destino. En otras palabras: el tipo no se cambia, pero el tipo en tiempo de compilación sí.
-
Cuando el objeto de origen no implementa la interfaz de destino, el objeto de origen es convertido en un objeto que implementa la interfaz de destino. Entonces se cambian tanto el tipo como el tipo en tiempo de compilación.
Déjame mostrarte esto con algunos ejemplos. Tengo este pequeño método que informa el tipo en tiempo de compilación y el tipo real de un objeto (cortesía de Jon Skeet):
void ReportTypeProperties(T obj)
Console.WriteLine("Compile-time type: 0", typeof(T).Name);
Console.WriteLine("Actual type: 0", obj.GetType().Name);
Probemos un linq-to-sql arbitrario Table
, que implementa IQueryable
:
ReportTypeProperties(context.Observations);
ReportTypeProperties(context.Observations.AsEnumerable());
ReportTypeProperties(context.Observations.AsQueryable());
El 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
Verá que la clase de tabla en sí misma siempre se devuelve, pero su representación cambia.
Ahora un objeto que implementa IEnumerable
, no IQueryable
:
var ints = new[] 1, 2 ;
ReportTypeProperties(ints);
ReportTypeProperties(ints.AsEnumerable());
ReportTypeProperties(ints.AsQueryable());
Los 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
Ahí está. AsQueryable()
ha convertido el array en una EnumerableQuery
, que “representa un IEnumerable
colección como IQueryable
fuente de datos “(MSDN).
¿Cual es el uso?
AsEnumerable
se utiliza con frecuencia para cambiar de cualquier IQueryable
implementación a LINQ to objects (L2O), principalmente porque el primero no admite funciones que tiene L2O. Para obtener más detalles, consulte ¿Cuál es el efecto de AsEnumerable () en una entidad LINQ ?.
Por ejemplo, en una consulta de Entity Framework solo podemos usar un número restringido de métodos. Entonces, si, por ejemplo, necesitamos usar uno de nuestros propios métodos en una consulta, normalmente escribiríamos algo como
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => MySuperSmartMethod(x))
ToList
– que convierte un IEnumerable
a un List
– también se utiliza a menudo para este propósito. La ventaja de usar AsEnumerable
vs. ToList
es eso AsEnumerable
no ejecuta la consulta. AsEnumerable
conserva la ejecución diferida y no crea una lista intermedia a menudo inútil.
Por otro lado, cuando se desea la ejecución forzada de una consulta LINQ, ToList
puede ser una forma de hacerlo.
AsQueryable
se puede utilizar para hacer que una colección enumerable acepte expresiones en declaraciones LINQ. Vea aquí para más detalles: ¿Realmente necesito usar AsQueryable () en la colección ?.
¡Nota sobre el abuso de sustancias!
AsEnumerable
funciona como una droga. Es una solución rápida, pero tiene un costo y no aborda el problema subyacente.
En muchas respuestas de Stack Overflow, veo personas que solicitan AsEnumerable
para solucionar casi cualquier problema con métodos no admitidos en expresiones LINQ. Pero el precio no siempre está claro. Por ejemplo, si haces esto:
context.MyLongWideTable // A table with many records and columns
.Where(x => x.Type == "type")
.Select(x => new x.Name, x.CreateDate )
… todo está perfectamente traducido a una declaración SQL que filtros (Where
) y proyectos (Select
). Es decir, se reducen tanto la longitud como la anchura, respectivamente, del conjunto de resultados de SQL.
Ahora suponga que los usuarios solo quieren ver la parte de la fecha de CreateDate
. En Entity Framework, descubrirá rápidamente que …
.Select(x => new x.Name, x.CreateDate.Date )
… no es compatible (en el momento de escribir este artículo). Ah, afortunadamente existe el AsEnumerable
reparar:
context.MyLongWideTable.AsEnumerable()
.Where(x => x.Type == "type")
.Select(x => new x.Name, x.CreateDate.Date )
Seguro, probablemente funciona. Pero saca toda la tabla a la memoria y luego aplica el filtro y las proyecciones. Bueno, la mayoría de la gente es lo suficientemente inteligente como para hacer Where
primero:
context.MyLongWideTable
.Where(x => x.Type == "type").AsEnumerable()
.Select(x => new x.Name, x.CreateDate.Date )
Pero aún así, todas las columnas se recuperan primero y la proyección se realiza en la memoria.
La verdadera solución es:
context.MyLongWideTable
.Where(x => x.Type == "type")
.Select(x => new x.Name, DbFunctions.TruncateTime(x.CreateDate) )
(Pero eso requiere solo un poco más de conocimiento …)
¿Qué NO hacen estos métodos?
Restaurar las capacidades de IQueryable
Ahora una advertencia importante. Cuando tu lo hagas
context.Observations.AsEnumerable()
.AsQueryable()
terminará con el objeto fuente representado como IQueryable
. (Porque ambos métodos solo emiten y no convierten).
Pero cuando lo haces
context.Observations.AsEnumerable().Select(x => x)
.AsQueryable()
cual sera el resultado?
los Select
produce un WhereSelectEnumerableIterator
. Esta es una clase .Net interna que implementa IEnumerable
, no IQueryable
. Entonces se ha producido una conversión a otro tipo y la subsiguiente AsQueryable
Ya no se puede devolver la fuente original.
La implicación de esto es que usar AsQueryable
es no es una forma de inyectar mágicamente un proveedor de consultas con sus características específicas en un enumerable. Suponga que lo hace
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => x.ToString())
.AsQueryable()
.Where(...)
La condición where nunca se traducirá a SQL. AsEnumerable()
seguido de declaraciones LINQ definitivamente corta la conexión con el proveedor de consultas del marco de la entidad.
Muestro deliberadamente este ejemplo porque he visto preguntas aquí donde la gente, por ejemplo, intenta ‘inyectar’ Include
capacidades en una colección llamando AsQueryable
. Se compila y se ejecuta, pero no hace nada porque el objeto subyacente no tiene un Include
implementación más.
Ejecutar
Ambos AsQueryable
y AsEnumerable
no ejecutar (o enumerar) el objeto de origen. Solo cambian su tipo o representación. Ambas interfaces involucradas, IQueryable
y IEnumerable
, no son más que “una enumeración que espera suceder”. No se ejecutan antes de que se vean obligados a hacerlo, por ejemplo, como se mencionó anteriormente, llamando ToList()
.
Eso significa que ejecutar un IEnumerable
obtenido llamando AsEnumerable
en una IQueryable
objeto, ejecutará el subyacente IQueryable
. Una ejecución posterior de la IEnumerable
ejecutará de nuevo el IQueryable
. Lo que puede resultar muy caro.
Implementaciones específicas
Hasta ahora, esto solo se trataba de Queryable.AsQueryable
y Enumerable.AsEnumerable
métodos de extensión. Pero, por supuesto, cualquiera puede escribir métodos de instancia o métodos de extensión con los mismos nombres (y funciones).
De hecho, un ejemplo común de un AsEnumerable
el método de extensión es DataTableExtensions.AsEnumerable
. DataTable
no implementa IQueryable
o IEnumerable
, por lo que los métodos de extensión habituales no se aplican.
Listar()
- Ejecute la consulta inmediatamente
AsEnumerable ()
- perezoso (ejecutar la consulta más tarde)
- Parámetro:
Func
- Carga CADA grabar en la memoria de la aplicación y luego manipularlos / filtrarlos. (por ejemplo, Dónde / Tomar / Saltar, seleccionará * de la tabla1, en la memoria, luego seleccionará los primeros elementos X) (En este caso, lo que hizo: Linq-to-SQL + Linq-to-Object)
AsQueryable ()
- perezoso (ejecutar la consulta más tarde)
- Parámetro:
Expression
> - Convierta Expression en T-SQL (con el proveedor específico), consulte de forma remota y cargue el resultado en la memoria de su aplicación.
- Es por eso que DbSet (en Entity Framework) también hereda IQueryable para obtener la consulta eficiente.
- No cargue todos los registros, por ejemplo, si Take (5), generará una selección de los 5 principales * SQL en segundo plano. Esto significa que este tipo es más amigable para la base de datos SQL, y es por eso que este tipo generalmente tiene un mayor rendimiento y se recomienda cuando se trata de una base de datos.
- Entonces
AsQueryable()
generalmente funciona mucho más rápido queAsEnumerable()
ya que genera T-SQL al principio, que incluye todas sus condiciones where en su Linq.
ToList () será todo en la memoria y luego estará trabajando en ello. entonces, ToList (). donde (aplicar algún filtro) se ejecuta localmente. AsQueryable () ejecutará todo de forma remota, es decir, se envía un filtro a la base de datos para su aplicación. Queryable no hace nada hasta que lo ejecuta. ToList, sin embargo, se ejecuta inmediatamente.
Además, mire esta respuesta ¿Por qué usar AsQueryable () en lugar de List () ?.
EDITAR: Además, en su caso, una vez que haga ToList (), todas las operaciones posteriores son locales, incluido AsQueryable (). No puede cambiar a remoto una vez que comience a ejecutar localmente. Espero que esto lo aclare un poco más.