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

Conozco algunas diferencias de LINQ a Entidades y LINQ a Objetos que el primero implementa IQueryable y el segundo implementa IEnumerable y mi scope de pregunta está dentro de EF 5.

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

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

  2. ¿Hay algún problema de rendimiento o algo que conduzca al uso de uno sobre el otro?

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

Hay mucho que decir sobre esto. Permítanme centrarme en AsEnumerable y AsQueryable y mencionar ToList() en el camino.

¿Qué hacen estos métodos?

AsEnumerable y AsQueryable emiten o convierten a IQueryable o IEnumerable , respectivamente. Digo lanzar 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 envía a la interfaz de destino. En otras palabras: el tipo no se cambia, pero el tipo de tiempo de comstackción es.

  • 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 de tiempo de comstackción.

Déjame mostrar esto con algunos ejemplos. Tengo este pequeño método que informa el tipo de tiempo de comstackció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 una tabla arbitraria de linq-a-sql 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í 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 una EnumerableQuery , que “representa una colección IQueryable como una fuente de datos IEnumerable “. (MSDN).

¿Cual es el uso?

AsEnumerable se usa con frecuencia para pasar de una implementación IQueryable 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 así como

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

ToList – que convierte un IEnumerable en una List – a menudo se usa también para este propósito. La ventaja de usar AsEnumerable vs. ToList es que AsEnumerable no ejecuta la consulta. AsEnumerable preserva 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 las 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 un medicamento. Es una solución rápida, pero a un costo y no aborda el problema subyacente.

En muchas respuestas de desbordamiento de stack, veo personas que aplican AsEnumerable para 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 statement SQL que filtra ( Where ) y proyectos ( Select ). Es decir, tanto la longitud como el ancho, respectivamente, del conjunto de resultados de SQL se reducen.

Ahora suponga que los usuarios solo desean ver la fecha como parte de CreateDate . En Entity Framework, descubrirá rápidamente que …

 .Select(x => new { x.Name, x.CreateDate.Date }) 

… no es compatible (al momento de escribir). Ah, afortunadamente, está la solución AsEnumerable :

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

Claro, se ejecuta, probablemente. Pero extrae toda la tabla en 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 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 verdadera solución 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?

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 Select produce un WhereSelectEnumerableIterator . Esta es una clase .Net interna que implementa IEnumerable , no IQueryable . Por lo tanto, se ha llevado a cabo una conversión a otro tipo y el AsQueryable subsiguiente AsQueryable no puede devolver la fuente original.

La implicación de esto es que usar AsQueryable no es 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 where nunca se traducirá a SQL. AsEnumerable() seguido de las declaraciones LINQ corta definitivamente la conexión con el proveedor de consultas de marco de entidad.

Deliberadamente, muestro este ejemplo porque he visto preguntas aquí donde las personas, por ejemplo, intentan ‘inyectar’ capacidades de Include en una colección llamando a AsQueryable . Se comstack y se ejecuta, pero no hace nada porque el objeto subyacente ya no tiene una implementación Include .

Implementaciones específicas

Hasta ahora, esto solo se trataba de los métodos de extensión Queryable.AsQueryable y 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 método de extensión AsEnumerable específico es DataTableExtensions.AsEnumerable . DataTable no implementa IEnumerable o IQueryable , por lo que los métodos de extensión normales no se aplican.

Listar()

  • Ejecuta la consulta de inmediato

AsEnumerable ()

  • flojo (ejecutar la consulta más tarde)
  • Parámetro: Func
  • Cargue TODOS los registros en la memoria de la aplicación y luego manipúlelos / fíltrelos. (Por ejemplo, Where / Take / Skip, seleccionará * de la tabla 1, en la memoria, luego seleccionará los primeros elementos X) (en este caso, lo que hizo: Linq-to-SQL + Linq-to-Object)

AsQueryable ()

  • flojo (ejecutar la consulta más tarde)
  • Parámetro: Expression>
  • Convierta Expresión 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 cada registro, por ejemplo, si toma (5), generará seleccionar 5 principales SQL en el fondo. Esto significa que este tipo es más amigable con 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.
  • Por AsQueryable() tanto, AsQueryable() generalmente funciona mucho más rápido que AsEnumerable() ya que genera T-SQL al principio, que incluye todas las condiciones de tu Linq.

ToList () será todo en la memoria y luego estará trabajando en ello. entonces, ToList (). where (aplicar algún filtro) se ejecuta localmente. AsQueryable () ejecutará todo de forma remota, es decir, se enviará un filtro a la base de datos para su aplicación. Queryable no hace nada hasta que lo ejecutas. ToList, sin embargo, se ejecuta inmediatamente.

Además, mira esta respuesta. ¿Por qué usar AsQueryable () en lugar de List ()? .

EDITAR: Además, en su caso una vez que haga ToList (), cada operación subsiguiente es local, incluyendo AsQueryable (). No puede cambiar a control remoto una vez que comience a ejecutar localmente. Espero que esto lo haga un poco más claro.