Devolver IEnumerable <T> frente a IQueryable <T>


1085

¿Cuál es la diferencia entre regresar IQueryable<T>vs. IEnumerable<T>, cuándo se debe preferir uno sobre el otro?

IQueryable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

IEnumerable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

Respuestas:


1778

Sí, ambos te darán ejecución diferida .

La diferencia es que IQueryable<T>es la interfaz que permite que LINQ-to-SQL (LINQ.-to-anything realmente) funcione. Entonces, si refina aún más su consulta en un IQueryable<T>, esa consulta se ejecutará en la base de datos, si es posible.

Para el IEnumerable<T>caso, será LINQ-to-object, lo que significa que todos los objetos que coincidan con la consulta original deberán cargarse en la memoria desde la base de datos.

En codigo:

IQueryable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);

Ese código ejecutará SQL solo para seleccionar clientes de oro. El siguiente código, por otro lado, ejecutará la consulta original en la base de datos, y luego filtrará los clientes que no son de oro en la memoria:

IEnumerable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);

Esta es una diferencia bastante importante, y trabajar IQueryable<T>en muchos casos puede evitar que devuelva demasiadas filas de la base de datos. Otro buen ejemplo es hacer paginación: si utiliza Takey Skipen IQueryable, se consigue solamente el número de filas solicitado; hacer eso en un IEnumerable<T>hará que todas sus filas se carguen en la memoria.


32
Gran explicación ¿Hay alguna situación en la que IEnumerable sería preferible a IQueryable?
fjxx

8
Entonces, podemos decir que si estamos usando IQueryable para consultar objetos de memoria, ¿no habrá diferencia entre IEnumerable e IQueryable?
Tarik

11
ADVERTENCIA: Si bien IQueryable podría ser una solución tentadora debido a la optimización indicada, no se debe permitir que pase el repositorio o la capa de servicio. Esto es para proteger su base de datos de la sobrecarga causada por "apilar expresiones LINQ".
Yorro

48
@fjxx Sí. Si desea un filtrado repetido en su resultado original (varios resultados finales). Hacer eso en la interfaz IQueryable hará varios viajes de ida y vuelta a la base de datos, donde al hacerlo en IEnumerable se filtrará en la memoria, haciéndolo más rápido (a menos que la cantidad de datos sea ENORME)
Per Hornshøj-Schierbeck

34
Otra razón para preferir IEnumerablea IQueryablees que no todas las operaciones de LINQ son compatibles con todos los proveedores de LINQ. Por lo tanto, siempre que sepa lo que está haciendo, puede usar IQueryablepara enviar la mayor parte de la consulta al proveedor de LINQ (LINQ2SQL, EF, NHibernate, MongoDB, etc.). Pero si deja que otro código haga lo que quiera con IQueryableusted, eventualmente terminará en problemas porque algún código de cliente en alguna parte utilizó una operación no admitida. Estoy de acuerdo con la recomendación de no publicar IQueryables "en la naturaleza" más allá del repositorio o capa equivalente.
Avish

302

La respuesta principal es buena, pero no menciona los árboles de expresión que explican "cómo" difieren las dos interfaces. Básicamente, hay dos conjuntos idénticos de extensiones LINQ. Where(), Sum(), Count(), FirstOrDefault(), Etc todos tienen dos versiones: una que acepta funciones y uno que acepta expresiones.

  • La IEnumerablefirma de la versión es:Where(Func<Customer, bool> predicate)

  • La IQueryablefirma de la versión es:Where(Expression<Func<Customer, bool>> predicate)

Probablemente has estado usando ambos sin darte cuenta porque ambos se llaman usando una sintaxis idéntica:

por ejemplo, Where(x => x.City == "<City>")trabaja en ambos IEnumerableyIQueryable

  • Cuando se usa Where()en una IEnumerablecolección, el compilador pasa una función compilada aWhere()

  • Cuando se usa Where()en una IQueryablecolección, el compilador pasa un árbol de expresión a Where(). Un árbol de expresión es como el sistema de reflexión pero para el código. El compilador convierte su código en una estructura de datos que describe lo que hace su código en un formato fácilmente digerible.

¿Por qué molestarse con esta cosa del árbol de expresión? Solo quiero Where()filtrar mis datos. La razón principal es que tanto el ORM de EF como el de Linq2SQL pueden convertir árboles de expresión directamente en SQL, donde su código se ejecutará mucho más rápido.

Oh, eso suena como un aumento de rendimiento gratuito, ¿debería usar AsQueryable()todo el lugar en ese caso? No, IQueryablesolo es útil si el proveedor de datos subyacente puede hacer algo con él. La conversión de algo así como un habitual Lista IQueryableno le dará ningún beneficio.


99
OMI es mejor que la respuesta aceptada. Sin embargo, no entiendo nada: IQueryable no ofrece ningún beneficio para los objetos normales, está bien, pero ¿es peor de alguna manera? Porque si simplemente no da ningún beneficio, no es razón suficiente para preferir IEnumerable, por lo que la idea de usar IQueryable en todo el lugar sigue siendo válida.
Sergei Tachenov el

1
Sergey, IQueryable extiende IEnumerable, de modo que cuando usas IQueryable, cargas más en la memoria de lo que lo haría una instanciación IEnumerable. Así que aquí hay un argumento. ( stackoverflow.com/questions/12064828/… c ++ aunque pensé que podría extrapolar esto)
Vikingo

Estar de acuerdo con Sergei en que esta es la mejor respuesta (aunque la respuesta aceptada está bien). Yo añadiría que, en mi experiencia, IQueryableno las funciones de análisis sintáctico, así como IEnumerablelo hace: por ejemplo, si usted quiere saber qué elementos de una DBSet<BookEntity>no están en una List<BookObject>, dbSetObject.Where(e => !listObject.Any(o => o.bookEntitySource == e))emite una excepción: Expression of type 'BookEntity' cannot be used for parameter of type 'BookObject' of method 'Boolean Contains[BookObject] (IEnumerable[BookObject], BookObject)'. Tuve que agregar .ToList()después dbSetObject.
Jean-David Lanz

80

Sí, ambos usan ejecución diferida. Vamos a ilustrar la diferencia usando el generador de perfiles de SQL Server ...

Cuando ejecutamos el siguiente código:

MarketDevEntities db = new MarketDevEntities();

IEnumerable<WebLog> first = db.WebLogs;
var second = first.Where(c => c.DurationSeconds > 10);
var third = second.Where(c => c.WebLogID > 100);
var result = third.Where(c => c.EmailAddress.Length > 11);

Console.Write(result.First().UserName);

En el perfilador de SQL Server encontramos un comando igual a:

"SELECT * FROM [dbo].[WebLog]"

Se tarda aproximadamente 90 segundos en ejecutar ese bloque de código en una tabla WebLog que tiene 1 millón de registros.

Por lo tanto, todos los registros de la tabla se cargan en la memoria como objetos, y luego con cada .Where () habrá otro filtro en la memoria contra estos objetos.

Cuando usamos en IQueryablelugar de IEnumerableen el ejemplo anterior (segunda línea):

En el perfilador de SQL Server encontramos un comando igual a:

"SELECT TOP 1 * FROM [dbo].[WebLog] WHERE [DurationSeconds] > 10 AND [WebLogID] > 100 AND LEN([EmailAddress]) > 11"

Se tarda aproximadamente cuatro segundos en ejecutar este bloque de código usando IQueryable.

IQueryable tiene una propiedad llamada Expressionque almacena una expresión de árbol que comienza a crearse cuando usamos resulten nuestro ejemplo (que se llama ejecución diferida), y al final esta expresión se convertirá en una consulta SQL para ejecutarse en el motor de la base de datos.


55
Esto me enseña cuando se convierte a IEnumerable, el IQueryable subyacente pierde su método de extensión IQueryable.
Yiping

56

Ambos te darán ejecución diferida, sí.

En cuanto a cuál se prefiere sobre el otro, depende de cuál sea su fuente de datos subyacente.

Si devuelve un IEnumerable, forzará automáticamente el tiempo de ejecución a usar LINQ to Objects para consultar su colección.

Devolver un IQueryable(que implementa IEnumerable, por cierto) proporciona la funcionalidad adicional para traducir su consulta en algo que podría funcionar mejor en la fuente subyacente (LINQ to SQL, LINQ to XML, etc.).


30

En términos generales, recomendaría lo siguiente:

  • Regrese IQueryable<T>si desea habilitar al desarrollador utilizando su método para refinar la consulta que devuelve antes de ejecutar.

  • Regrese IEnumerablesi desea transportar un conjunto de objetos para enumerar.

Imagina IQueryableque es lo que es: una "consulta" de datos (que puedes refinar si quieres). Un IEnumerablees un conjunto de objetos (que ya se recibió o se creó) sobre los cuales puede enumerar.


2
"Puede enumerar," no "puede IEnumerable".
Casey

28

Mucho se ha dicho anteriormente, pero volviendo a las raíces, de una manera más técnica:

  1. IEnumerable es una colección de objetos en memoria que puede enumerar , una secuencia en memoria que permite iterar (facilita el foreachbucle interno , aunque IEnumeratorsolo puede hacerlo ). Residen en la memoria como es.
  2. IQueryable es un árbol de expresión que se traducirá a otra cosa en algún momento con capacidad de enumerar sobre el resultado final . Supongo que esto es lo que confunde a la mayoría de las personas.

Obviamente tienen diferentes connotaciones.

IQueryablerepresenta un árbol de expresión (una consulta, simplemente) que el proveedor de consultas subyacente traducirá a otra cosa tan pronto como se invoquen las API de lanzamiento, como las funciones agregadas de LINQ (Sum, Count, etc.) o ToList [Array, Dictionary ,. ..]. Y los IQueryableobjetos también se implementan IEnumerable, de IEnumerable<T>modo que si representan una consulta, el resultado de esa consulta podría iterarse. Significa que IQueryable no tiene que ser solo consultas. El término correcto es que son árboles de expresión .

Ahora, cómo se ejecutan esas expresiones y a qué recurren todo depende de los llamados proveedores de consultas (ejecutores de expresiones en los que podemos pensar).

En el mundo de Entity Framework (que es ese proveedor de fuente de datos subyacente místico, o el proveedor de consultas), las IQueryableexpresiones se traducen en consultas T-SQL nativas . Nhibernatehace cosas similares con ellos. Puede escribir el suyo siguiendo los conceptos bastante bien descritos en LINQ: por ejemplo, crear un enlace de proveedor IQueryable , y es posible que desee tener una API de consulta personalizada para su servicio de proveedor de tienda de productos.

Básicamente, los IQueryableobjetos se construyen todo el tiempo hasta que los liberamos explícitamente y le decimos al sistema que los reescriba en SQL o lo que sea y envíe la cadena de ejecución para su procesamiento posterior.

Como si se tratara de una ejecución diferida , es una LINQcaracterística mantener el esquema del árbol de expresión en la memoria y enviarlo a la ejecución solo a pedido, siempre que se invoquen ciertas API contra la secuencia (el mismo Count, ToList, etc.).

El uso adecuado de ambos depende en gran medida de las tareas que enfrenta para el caso específico. Para el conocido patrón de repositorio, personalmente opto por volver IList, eso es IEnumerablesobre Listas (indexadores y similares). Por lo tanto, es mi consejo usar IQueryablesolo dentro de repositorios e IEnumerable en cualquier otro lugar del código. No decir sobre las preocupaciones de comprobabilidad que IQueryablerompe y arruina el principio de separación de preocupaciones . Si devuelve una expresión desde dentro de los repositorios, los consumidores pueden jugar con la capa de persistencia como desearían.

Una pequeña adición al desorden :) (de una discusión en los comentarios)) Ninguno de ellos son objetos en la memoria ya que no son tipos reales per se, son marcadores de un tipo, si quieres profundizar tanto. Pero tiene sentido (y es por eso que incluso MSDN lo expresó de esta manera) pensar en IEnumerables como colecciones en memoria, mientras que IQueryables como árboles de expresión. El punto es que la interfaz IQueryable hereda la interfaz IEnumerable de modo que si representa una consulta, los resultados de esa consulta se pueden enumerar. La enumeración hace que se ejecute el árbol de expresión asociado con un objeto IQueryable. Entonces, de hecho, no se puede llamar a ningún miembro IEnumerable sin tener el objeto en la memoria. Entrará allí si lo hace, de todos modos, si no está vacío. IQueryables son solo consultas, no los datos.


3
El comentario de que IEnumerables siempre están en la memoria no es necesariamente cierto. La interfaz IQueryable implementa la interfaz IEnumerable. Debido a esto, puede pasar un IQueryable sin procesar que representa una consulta LINQ-to-SQL directamente en una vista que espera un IEnumerable! Es posible que se sorprenda al descubrir que su contexto de datos ha expirado o que termina teniendo problemas con MARS (múltiples conjuntos de resultados activos).

entonces, de hecho, no se puede llamar a ningún miembro IEnumerable sin tener el objeto en la memoria. Entrará allí si lo hace, de todos modos, si no está vacío. IQueryables son solo consultas, no los datos. Pero realmente entiendo tu punto. Voy a agregar un comentario sobre esto.
Arman McHitarian

@AlexanderPritchard ninguno de ellos son objetos en la memoria ya que no son tipos reales per se, son marcadores de un tipo, si quieres profundizar tanto. Pero tiene sentido (y es por eso que incluso MSDN lo expresó de esta manera) pensar en IEnumerables como colecciones en memoria, mientras que IQueryables como árboles de expresión. El punto es que la interfaz IQueryable hereda la interfaz IEnumerable de modo que si representa una consulta, los resultados de esa consulta se pueden enumerar. La enumeración hace que se ejecute el árbol de expresión asociado con un objeto IQueryable.
Arman McHitarian

24

En general, desea conservar el tipo estático original de la consulta hasta que sea importante.

Por esta razón, puede definir su variable como 'var' en lugar de cualquiera IQueryable<>o IEnumerable<>y sabrá que no está cambiando el tipo.

Si comienza con un IQueryable<>, generalmente desea mantenerlo como IQueryable<>hasta que haya alguna razón convincente para cambiarlo. La razón de esto es que desea darle al procesador de consultas tanta información como sea posible. Por ejemplo, si solo va a usar 10 resultados (que ha llamado Take(10)), entonces desea que SQL Server sepa sobre eso para que pueda optimizar sus planes de consulta y enviarle solo los datos que usará.

Una razón convincente para cambiar el tipo de IQueryable<>a IEnumerable<>podría ser que está llamando a alguna función de extensión que la implementación de IQueryable<>su objeto en particular no puede manejar o maneja de manera ineficiente. En ese caso, es posible que desee convertir el tipo a IEnumerable<>(asignando a una variable de tipo IEnumerable<>o utilizando el AsEnumerablemétodo de extensión, por ejemplo) para que las funciones de extensión que llame terminen siendo las de la Enumerableclase en lugar de lasQueryable clase.


18

Hay una publicación de blog con una breve muestra del código fuente sobre cómo el mal uso de IEnumerable<T>puede afectar drásticamente el rendimiento de la consulta LINQ: Entity Framework: IQueryable vs. IEnumerable .

Si profundizamos y observamos las fuentes, podemos ver que obviamente existen diferentes métodos de extensión para IEnumerable<T>:

// Type: System.Linq.Enumerable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Enumerable
{
    public static IEnumerable<TSource> Where<TSource>(
        this IEnumerable<TSource> source, 
        Func<TSource, bool> predicate)
    {
        return (IEnumerable<TSource>) 
            new Enumerable.WhereEnumerableIterator<TSource>(source, predicate);
    }
}

y IQueryable<T>:

// Type: System.Linq.Queryable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(
        this IQueryable<TSource> source, 
        Expression<Func<TSource, bool>> predicate)
    {
        return source.Provider.CreateQuery<TSource>(
            Expression.Call(
                null, 
                ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(
                    new Type[] { typeof(TSource) }), 
                    new Expression[] 
                        { source.Expression, Expression.Quote(predicate) }));
    }
}

El primero devuelve un iterador enumerable, y el segundo crea una consulta a través del proveedor de consultas, especificado en la IQueryablefuente.


11

Recientemente me encontré con un problema con IEnumerablev IQueryable. El algoritmo que se utiliza primero realizó una IQueryableconsulta para obtener un conjunto de resultados. Luego se pasaron a un foreachbucle, con los elementos instanciados como una clase Entity Framework (EF). Esta clase EF se usó luego en la fromcláusula de una consulta Linq to Entity, lo que provocó que el resultado fueraIEnumerable .

Soy bastante nuevo en EF y Linq para Entidades, por lo que me llevó un tiempo descubrir cuál era el cuello de botella. Usando MiniProfiling, encontré la consulta y luego convertí todas las operaciones individuales en una sola consulta IQueryableLinq para Entidades. La IEnumerabletomó 15 segundos y la IQueryabletomó 0.5 segundos en ejecutarse. Hubo tres tablas involucradas y, después de leer esto, creo que la IEnumerableconsulta en realidad estaba formando un producto cruzado de tres tablas y filtrando los resultados.

Intente utilizar IQueryables como regla general y haga un perfil de su trabajo para que sus cambios sean medibles.


la razón fue que las expresiones IQueryable se convierten a un SQL nativo en EF y se ejecutan directamente en la base de datos mientras que las listas IEnumerable son objetos en memoria. Se obtienen del DB en algún momento cuando se llaman funciones agregadas como Count, Sum o cualquier To ... y luego operan en la memoria. IQueryables también están atascados en la memoria una vez que ha llamado a una de esas API, pero si no, puede pasar la expresión a la pila de las capas y jugar con los filtros hasta que la API llame. DAL bien diseñado como un repositorio bien diseñado resolverá este tipo de problemas;)
Arman McHitarian 05 de

10

Me gustaría aclarar algunas cosas debido a respuestas aparentemente conflictivas (principalmente en torno a IEnumerable).

(1) IQueryableextiende la IEnumerableinterfaz. (Puede enviar un mensaje IQueryablea algo que se espera IEnumerablesin error).

(2) Ambos IQueryabley IEnumerableLINQ intentan una carga diferida al iterar sobre el conjunto de resultados. (Tenga en cuenta que la implementación se puede ver en los métodos de extensión de interfaz para cada tipo).

En otras palabras, IEnumerablesno son exclusivamente "en memoria". IQueryablesno siempre se ejecutan en la base de datos. IEnumerabledebe cargar cosas en la memoria (una vez recuperado, posiblemente de forma perezosa) porque no tiene un proveedor de datos abstractos. IQueryablesconfíe en un proveedor abstracto (como LINQ-to-SQL), aunque este también podría ser el proveedor en memoria .NET.

Caso de uso de muestra

(a) Recupere la lista de registros IQueryabledel contexto EF. (No hay registros en la memoria).

(b) Pase el IQueryablea una vista cuyo modelo es IEnumerable. (Válido. Se IQueryableextiendeIEnumerable )

(c) Iterar y acceder a los registros del conjunto de datos, entidades secundarias y propiedades desde la vista. (¡Puede causar excepciones!)

Posibles problemas

(1) Los IEnumerableintentos de carga diferida y su contexto de datos expiraron. Se produjo una excepción porque el proveedor ya no está disponible.

(2) Los proxies de entidad de Entity Framework están habilitados (el valor predeterminado) e intenta acceder a un objeto relacionado (virtual) con un contexto de datos caducado. Igual que (1).

(3) Conjuntos de resultados activos múltiples (MARS). Si está iterando sobre IEnumerableun foreach( var record in resultSet )bloque e intenta acceder simultáneamente record.childEntity.childProperty, puede terminar con MARS debido a la carga diferida tanto del conjunto de datos como de la entidad relacional. Esto provocará una excepción si no está habilitado en su cadena de conexión.

Solución

  • He descubierto que habilitar MARS en la cadena de conexión funciona de manera poco confiable. Le sugiero que evite MARS a menos que se comprenda bien y se desee explícitamente.

Ejecute la consulta y almacene los resultados invocando. resultList = resultSet.ToList() Esta parece ser la forma más directa de garantizar que sus entidades estén en la memoria.

En los casos en que está accediendo a entidades relacionadas, aún puede requerir un contexto de datos. O eso, o puede deshabilitar proxies de entidad y Includeentidades explícitamente relacionadas de su DbSet.


9

La principal diferencia entre "IEnumerable" e "IQueryable" es acerca de dónde se ejecuta la lógica del filtro. Uno se ejecuta en el lado del cliente (en memoria) y el otro se ejecuta en la base de datos.

Por ejemplo, podemos considerar un ejemplo en el que tenemos 10,000 registros para un usuario en nuestra base de datos y digamos solo 900 de los cuales son usuarios activos, por lo que en este caso si usamos "IEnumerable", primero carga todos los 10,000 registros en la memoria y luego aplica el filtro IsActive que finalmente devuelve los 900 usuarios activos.

Mientras que, por otro lado, en el mismo caso, si usamos "IQueryable", aplicará directamente el filtro IsActive en la base de datos, que directamente desde allí devolverá los 900 usuarios activos.

Enlace de referencia


¿Cuál es optimizado y ligero en términos de rendimiento?
Sitecore Sam

@Sam "IQueryable" es más preferido en términos de optimización y peso ligero.
Tabish Usman

6

Podemos usar ambos de la misma manera, y solo son diferentes en el rendimiento.

IQueryable solo se ejecuta en la base de datos de manera eficiente. Significa que crea una consulta de selección completa y solo obtiene los registros relacionados.

Por ejemplo, queremos tomar los 10 principales clientes cuyo nombre comienza con 'Nimal'. En este caso, la consulta de selección se generará como select top 10 * from Customer where name like ‘Nimal%’.

Pero si usáramos IEnumerable, la consulta sería similar select * from Customer where name like ‘Nimal%’y los diez primeros se filtrarán en el nivel de codificación C # (obtiene todos los registros de clientes de la base de datos y los pasa a C #).


5

Además de las primeras 2 respuestas realmente buenas (por driis y por Jacob):

La interfaz IEnumerable está en el espacio de nombres System.Collections.

El objeto IEnumerable representa un conjunto de datos en la memoria y solo puede avanzar sobre estos datos hacia adelante. La consulta representada por el objeto IEnumerable se ejecuta de forma inmediata y completa, por lo que la aplicación recibe datos rápidamente.

Cuando se ejecuta la consulta, IEnumerable carga todos los datos y, si necesitamos filtrarlos, el filtrado se realiza en el lado del cliente.

La interfaz IQueryable se encuentra en el espacio de nombres System.Linq.

El objeto IQueryable proporciona acceso remoto a la base de datos y le permite navegar a través de los datos en un orden directo de principio a fin o en orden inverso. En el proceso de creación de una consulta, el objeto devuelto es IQueryable, la consulta está optimizada. Como resultado, se consume menos memoria durante su ejecución, menos ancho de banda de red, pero al mismo tiempo se puede procesar un poco más lentamente que una consulta que devuelve un objeto IEnumerable.

Que elegir

Si necesita todo el conjunto de datos devueltos, es mejor usar IEnumerable, que proporciona la velocidad máxima.

Si NO necesita todo el conjunto de datos devueltos, sino solo algunos datos filtrados, entonces es mejor usar IQueryable.


0

Además de lo anterior, es interesante observar que puede obtener excepciones si usa en IQueryablelugar de IEnumerable:

Lo siguiente funciona bien si productses un IEnumerable:

products.Skip(-4);

Sin embargo, si productses un IQueryablee intenta acceder a los registros de una tabla de base de datos, obtendrá este error:

El desplazamiento especificado en una cláusula OFFSET puede no ser negativo.

Esto se debe a que se creó la siguiente consulta:

SELECT [p].[ProductId]
FROM [Products] AS [p]
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS

y OFFSET no puede tener un valor negativo.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.