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

182

Conozco algunas diferencias de LINQ to Entities y LINQ to Objects que el primer implementa IQueryabley el segundo implementa IEnumerabley el alcance de mi 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 el uso de combinaciones de ellos como .ToList().AsQueryable().

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

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

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

Amin Saqi
fuente

Respuestas:

354

Hay mucho que decir sobre esto. Te voy a contar AsEnumerabley AsQueryabley mención ToList()en el camino.

¿Qué hacen estos métodos?

AsEnumerabley AsQueryableemitir o convertir IEnumerableao 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?

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, 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 AsEnumerablevs. ToListes que AsEnumerableno ejecuta la consulta. AsEnumerableconserva 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 ser una forma de hacerlo.

AsQueryablese 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!

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 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 AsEnumerablesolució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 Whereprimero:

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 Selectproduce 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 AsQueryableya 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' Includecapacidades en una colección llamando AsQueryable. Se compila y se ejecuta, pero no hace nada porque el objeto subyacente ya no tiene una Includeimplementación.

Ejecutar

Ambos AsQueryabley AsEnumerableno ejecutan (ni enumeran ) el objeto fuente. Solo 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 verse obligados a hacerlo, por ejemplo, como se mencionó anteriormente, llamando ToList().

Eso significa que ejecutar un IEnumerableobtenido al invocar AsEnumerableun IQueryableobjeto, 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.AsQueryableyEnumerable.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 AsEnumerablemétodo de extensión específico es DataTableExtensions.AsEnumerable. DataTableno implementa IQueryableo IEnumerable, por lo que los métodos de extensión regulares no se aplican.

Gert Arnold
fuente
Gracias por la respuesta, ¿puede compartir su respuesta a la tercera pregunta de OP? 3. ¿Por qué uno usaría, por ejemplo, .ToList (). AsQueryable () en lugar de .AsQueryable ()? ?
kgzdev
@ikram No puedo pensar en nada donde eso sea útil. Como expliqué, la solicitud a AsQueryable()menudo se basa en un concepto erróneo. Pero dejaré que se cocine a fuego lento en la parte posterior de mi cabeza por un tiempo y veré si puedo agregar más cobertura sobre esa pregunta.
Gert Arnold
1
Gran respuesta. ¿Podría precisar qué sucede si el IEnumerable obtenido al llamar a AsEnumerable () en un IQueryable se enumera varias veces? ¿Se ejecutará la consulta varias veces o se reutilizarán los datos ya cargados de la base de datos en la memoria?
antoninod
@antoninod Buena idea. Hecho.
Gert Arnold
46

Listar()

  • Ejecute la consulta inmediatamente

AsEnumerable ()

  • perezoso (ejecute la consulta más tarde)
  • Parámetro: Func<TSource, bool>
  • Cargue TODOS los registros en la memoria de la aplicación, y luego manipúlelos / filtrelos. (por ejemplo, Where / Take / Skip, 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)

AsQueryable ()

  • perezoso (ejecute la consulta más tarde)
  • Parámetro: Expression<Func<TSource, bool>>
  • 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á seleccionar 5 top * SQL en segundo plano. 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()lo tanto, generalmente funciona mucho más rápido que AsEnumerable()al generar T-SQL al principio, que incluye todas las condiciones where en su Linq.
Xin
fuente
14

ToList () estará 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 de inmediato.

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

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

ashutosh raina
fuente
2
"AsQueryable () ejecutará todo de forma remota" Solo si el enumerable ya es consultable. De lo contrario, eso no es posible, y todo todavía se ejecuta localmente. La pregunta tiene ".... ToList (). AsQueryable ()", que podría usar algunas aclaraciones en su respuesta, IMO.
2

Encontró un mal desempeño en el siguiente código.

void DoSomething<T>(IEnumerable<T> objects){
    var single = objects.First(); //load everything into memory before .First()
    ...
}

Arreglado con

void DoSomething<T>(IEnumerable<T> objects){
    T single;
    if (objects is IQueryable<T>)
        single = objects.AsQueryable().First(); // SELECT TOP (1) ... is used
    else
        single = objects.First();

}

Para un IQueryable, quédese en IQueryable cuando sea posible, intente no ser usado como IEnumerable.

Actualización . Se puede simplificar aún más en una expresión, gracias Gert Arnold .

T single =  objects is IQueryable<T> q? 
                    q.First(): 
                    objects.First();
Rm558
fuente