¿Cuál es la diferencia entre .ToList(), .AsEnumerable(), AsQueryable()?

Resuelto Amin Saqi asked hace 11 años • 5 respuestas

Conozco algunas diferencias de LINQ to Entities y LINQ to Objects que implementa el primero IQueryabley el segundo IEnumerable, y el alcance de mi pregunta está dentro de EF 5.

Mi pregunta es ¿cuál es la diferencia técnica de esos 3 métodos? Veo que en muchas situaciones todos funcionan. También veo el uso de combinaciones de ellos como .ToList().AsQueryable().

  1. ¿Qué significan exactamente esos métodos?

  2. ¿Existe algún problema de rendimiento o algo que pueda llevar al uso de uno sobre el otro?

  3. ¿Por qué se usaría, por ejemplo, .ToList().AsQueryable()en lugar de .AsQueryable()?

Amin Saqi avatar Jul 31 '13 17:07 Amin Saqi
Aceptado

Hay mucho que decir sobre esto. Permítanme centrarme AsEnumerableen AsQueryabley mencionar ToList()a lo largo del camino.

¿Qué hacen estos métodos?

AsEnumerabley AsQueryableemitir o convertir a IEnumerableo 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 en sí se devuelve pero se transmite a la interfaz de destino. En otras palabras: el tipo no se cambia, pero sí el tipo en tiempo de compilación.

  • 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, se cambian tanto el tipo como el tipo en tiempo de compilación.

Permítanme mostrar 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>(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<T>, que implemente 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 siempre se devuelve la clase de tabla en sí, 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 un EnumerableQuery, que "representa una IEnumerable<T>colección como IQueryable<T>fuente de datos". (MSDN).

¿Cual es el uso?

AsEnumerablese usa con frecuencia para cambiar de cualquier IQueryableimplementació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, normalmente escribiríamos algo como

var query = context.Observations.Select(o => o.Id)
                   .AsEnumerable().Select(x => MySuperSmartMethod(x))

ToList – que convierte an IEnumerable<T>en a List<T>– también se utiliza a menudo para este propósito. La ventaja de usar AsEnumerablevs. ToListes que AsEnumerableno ejecuta la consulta. AsEnumerablepreserva 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, ToListpuede haber una forma de hacerlo.

AsQueryablese puede utilizar para hacer que una colección enumerable acepte expresiones en declaraciones LINQ. Consulte aquí para obtener más detalles: ¿Realmente necesito usar AsQueryable() en la colección? .

¡Nota sobre el abuso de sustancias!

AsEnumerablefunciona 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 AsEnumerablesolucionar 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 se traduce claramente en una declaración SQL que filtra ( Where) y proyecta ( Select). Es decir, se reducen tanto la longitud como la anchura, respectivamente, del conjunto de resultados de SQL.

Ahora supongamos que los usuarios solo quieren ver la parte de la fecha CreateDate. En Entity Framework descubrirás 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 la AsEnumerablesolución:

context.MyLongWideTable.AsEnumerable()
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, x.CreateDate.Date })

Claro, probablemente funcione. Pero guarda toda la tabla en la memoria y luego aplica el filtro y las proyecciones. Bueno, la mayoría de la gente es lo suficientemente inteligente como para hacer lo Whereprimero:

context.MyLongWideTable
       .Where(x => x.Type == "type").AsEnumerable()
       .Select(x => new { x.Name, x.CreateDate.Date })

Pero aún así se recuperan primero todas las columnas 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 sólo un poco más de conocimiento...)

¿Qué NO hacen estos métodos?

Restaurar 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()

¿Cuál será el resultado?

El Selectproduce un WhereSelectEnumerableIterator. Esta es una clase interna de .Net que implementa IEnumerable, noIQueryable . Entonces se ha producido una conversión a otro tipo y la subsiguiente AsQueryableya no puede devolver la fuente original.

La implicación de esto es que el uso no AsQueryablees una forma de inyectar mágicamente 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 donde nunca se traducirá a SQL. AsEnumerable()seguido de declaraciones 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í en las que la gente, por ejemplo, intenta "inyectar" Includecapacidades en una colección llamando a AsQueryable. Compila y ejecuta, pero no hace nada porque el objeto subyacente Includeya no tiene implementación.

Ejecutar

Ambos AsQueryabley AsEnumerableno ejecutan (ni enumeran ) el objeto fuente. Sólo cambian su tipo o representación. Ambas interfaces involucradas, IQueryabley IEnumerable, no son más que "una enumeración esperando a suceder". No se ejecutan antes de que se les obligue a hacerlo, por ejemplo, como se mencionó anteriormente, llamando a ToList().

Eso significa que ejecutar un IEnumerableobjeto obtenido al llamar AsEnumerablea un IQueryableobjeto ejecutará el subyacente IQueryable. Una ejecución posterior del IEnumerableejecutará nuevamente el IQueryable. Lo cual puede resultar muy caro.

Implementaciones específicas

Hasta ahora, esto se trataba sólo de los métodos de extensión Queryable.AsQueryabley Enumerable.AsEnumerable. 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 AsEnumerablemétodo de extensión específico es DataTableExtensions.AsEnumerable. DataTableno implementa IQueryableor IEnumerable, por lo que los métodos de extensión habituales no se aplican.

Gert Arnold avatar Aug 01 '2013 14:08 Gert Arnold

Listar()

  • Ejecutar la consulta inmediatamente.

ComoEnumerable()

  • perezoso (ejecuta la consulta más tarde)
  • Parámetro:Func<TSource, bool>
  • Cargue CADA registro en la memoria de la aplicación y luego maneje/filtrelos. (por ejemplo, Dónde/Tomar/Omitir, seleccionará * de la tabla1, en la memoria, luego seleccionará los primeros X elementos) (En este caso, lo que hizo: Linq-to-SQL + Linq-to-Object)

Como consultable()

  • perezoso (ejecuta la consulta más tarde)
  • Parámetro:Expression<Func<TSource, bool>>
  • Convierta Expression en T-SQL (con el proveedor específico), realice consultas 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 mejores * SQL en segundo plano. Esto significa que este tipo es más amigable con SQL Database y es por eso que este tipo generalmente tiene un mayor rendimiento y se recomienda cuando se trata de una base de datos.
  • Por lo AsQueryable()general, funciona mucho más rápido que AsEnumerable()cuando genera T-SQL al principio, lo que incluye todas las condiciones donde en su Linq.
Xin avatar Sep 28 '2015 16:09 Xin