Hay mucho que decir sobre esto. Te voy a contar AsEnumerable
y AsQueryable
y mención ToList()
en el camino.
¿Qué hacen estos métodos?
AsEnumerable
y AsQueryable
emitir o convertir IEnumerable
ao IQueryable
, respectivamente. Digo emitir o convertir con una razón:
Cuando el objeto de origen ya implementa la interfaz de destino, el objeto de origen se devuelve pero se convierte en la interfaz de destino. En otras palabras: el tipo no 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 se convierte en un objeto que implementa la interfaz de destino. Por lo tanto, tanto el tipo como el tipo de tiempo de compilación cambian.
Déjame mostrarte esto con algunos ejemplos. Tengo este pequeño método que informa el tipo de tiempo de compilación y el tipo real de un objeto ( cortesía 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);
}
Probemos con un arbitrario linq-to-sql Table<T>
, 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 la matriz en an EnumerableQuery
, que "representa una IEnumerable<T>
colección como IQueryable<T>
fuente de datos". (MSDN)
¿Cual es el uso?
AsEnumerable
se usa con frecuencia para cambiar de cualquier IQueryable
implementación a LINQ a objetos (L2O), principalmente porque la primera 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, típicamente escribiríamos algo como
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => MySuperSmartMethod(x))
ToList
- que convierte an en IEnumerable<T>
a List<T>
- a menudo también se usa para este propósito. La ventaja de usar AsEnumerable
vs. ToList
es que 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 usar para hacer que una colección enumerable acepte expresiones en sentencias LINQ. Vea aquí para más detalles: ¿Realmente necesito usar AsQueryable () en la colección? .
Nota sobre 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
solucionar casi cualquier problema con métodos no compatibles en expresiones LINQ. Pero el precio no siempre es 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 en una declaración SQL que filtra ( Where
) y proyecta ( Select
). Es decir, tanto la longitud como el ancho, respectivamente, del conjunto de resultados SQL se reducen.
Ahora suponga que los usuarios solo quieren ver la parte de la fecha CreateDate
. En Entity Framework descubrirá rápidamente que ...
.Select(x => new { x.Name, x.CreateDate.Date })
... no es compatible (en el momento de la escritura). Ah, afortunadamente está la AsEnumerable
solución:
context.MyLongWideTable.AsEnumerable()
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate.Date })
Claro, corre, probablemente. Pero arrastra toda la tabla a la memoria y luego aplica el filtro y las proyecciones. Bueno, la mayoría de las personas son lo suficientemente inteligentes como para hacer lo 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 obtienen primero y la proyección se realiza en la memoria.
La solución real es:
context.MyLongWideTable
.Where(x => x.Type == "type")
.Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) })
(Pero eso requiere 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ás con el objeto fuente representado como IQueryable
. (Porque ambos métodos solo lanzan y no convierten).
Pero cuando lo haces
context.Observations.AsEnumerable().Select(x => x)
.AsQueryable()
¿Cuál será el resultado?
El Select
produce a WhereSelectEnumerableIterator
. Esta es una clase interna .Net que implementa IEnumerable
, noIQueryable
. Por lo tanto, se ha realizado una conversión a otro tipo y el subsiguiente AsQueryable
ya no puede devolver la fuente original.
La implicación de esto es que el uso noAsQueryable
es una forma de inyectar mágicamente a un proveedor de consultas con sus características específicas en un enumerable. Supongamos que lo haces
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 las instrucciones LINQ corta definitivamente la conexión con el proveedor de consultas del marco de la entidad.
Muestro este ejemplo deliberadamente porque he visto preguntas aquí donde las personas, por ejemplo, intentan 'inyectar' Include
capacidades en una colección llamando AsQueryable
. Se compila y se ejecuta, pero no hace nada porque el objeto subyacente ya no tiene una Include
implementación.
Ejecutar
Ambos AsQueryable
y AsEnumerable
no ejecutan (ni enumeran ) el objeto fuente. Solo cambian su tipo o representación. Ambas interfaces involucradas, IQueryable
y IEnumerable
, no son más que "una enumeración esperando a suceder". No se ejecutan antes de verse obligados a hacerlo, por ejemplo, como se mencionó anteriormente, llamando ToList()
.
Eso significa que ejecutar un IEnumerable
obtenido al invocar AsEnumerable
un IQueryable
objeto, ejecutará el subyacente IQueryable
. Una posterior ejecución de laIEnumerable
voluntad ejecutará nuevamente el IQueryable
. Lo cual puede ser muy costoso.
Implementaciones Específicas
Hasta ahora, esto era solo sobre el Queryable.AsQueryable
yEnumerable.AsEnumerable
los 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
método de extensión específico es DataTableExtensions.AsEnumerable
. DataTable
no implementa IQueryable
o IEnumerable
, por lo que los métodos de extensión regulares no se aplican.