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 Take
y Skip
en 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.
IEnumerable
aIQueryable
es 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 usarIQueryable
para 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 conIQueryable
usted, 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 publicarIQueryable
s "en la naturaleza" más allá del repositorio o capa equivalente.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
IEnumerable
firma de la versión es:Where(Func<Customer, bool> predicate)
La
IQueryable
firma 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 ambosIEnumerable
yIQueryable
Cuando se usa
Where()
en unaIEnumerable
colección, el compilador pasa una función compilada aWhere()
Cuando se usa
Where()
en unaIQueryable
colección, el compilador pasa un árbol de expresión aWhere()
. 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,IQueryable
solo es útil si el proveedor de datos subyacente puede hacer algo con él. La conversión de algo así como un habitualList
aIQueryable
no le dará ningún beneficio.fuente
IQueryable
no las funciones de análisis sintáctico, así comoIEnumerable
lo hace: por ejemplo, si usted quiere saber qué elementos de unaDBSet<BookEntity>
no están en unaList<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ésdbSetObject
.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:
En el perfilador de SQL Server encontramos un comando igual a:
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
IQueryable
lugar deIEnumerable
en el ejemplo anterior (segunda línea):En el perfilador de SQL Server encontramos un comando igual a:
Se tarda aproximadamente cuatro segundos en ejecutar este bloque de código usando
IQueryable
.IQueryable tiene una propiedad llamada
Expression
que almacena una expresión de árbol que comienza a crearse cuando usamosresult
en 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.fuente
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 implementaIEnumerable
, 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.).fuente
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
IEnumerable
si desea transportar un conjunto de objetos para enumerar.Imagina
IQueryable
que es lo que es: una "consulta" de datos (que puedes refinar si quieres). UnIEnumerable
es un conjunto de objetos (que ya se recibió o se creó) sobre los cuales puede enumerar.fuente
Mucho se ha dicho anteriormente, pero volviendo a las raíces, de una manera más técnica:
IEnumerable
es una colección de objetos en memoria que puede enumerar , una secuencia en memoria que permite iterar (facilita elforeach
bucle interno , aunqueIEnumerator
solo puede hacerlo ). Residen en la memoria como es.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.
IQueryable
representa 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 losIQueryable
objetos también se implementanIEnumerable
, deIEnumerable<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
IQueryable
expresiones se traducen en consultas T-SQL nativas .Nhibernate
hace 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
IQueryable
objetos 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
LINQ
caracterí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 esIEnumerable
sobre Listas (indexadores y similares). Por lo tanto, es mi consejo usarIQueryable
solo dentro de repositorios e IEnumerable en cualquier otro lugar del código. No decir sobre las preocupaciones de comprobabilidad queIQueryable
rompe 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.
fuente
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<>
oIEnumerable<>
y sabrá que no está cambiando el tipo.Si comienza con un
IQueryable<>
, generalmente desea mantenerlo comoIQueryable<>
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 llamadoTake(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<>
aIEnumerable<>
podría ser que está llamando a alguna función de extensión que la implementación deIQueryable<>
su objeto en particular no puede manejar o maneja de manera ineficiente. En ese caso, es posible que desee convertir el tipo aIEnumerable<>
(asignando a una variable de tipoIEnumerable<>
o utilizando elAsEnumerable
método de extensión, por ejemplo) para que las funciones de extensión que llame terminen siendo las de laEnumerable
clase en lugar de lasQueryable
clase.fuente
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>
:y
IQueryable<T>
:El primero devuelve un iterador enumerable, y el segundo crea una consulta a través del proveedor de consultas, especificado en la
IQueryable
fuente.fuente
Recientemente me encontré con un problema con
IEnumerable
vIQueryable
. El algoritmo que se utiliza primero realizó unaIQueryable
consulta para obtener un conjunto de resultados. Luego se pasaron a unforeach
bucle, con los elementos instanciados como una clase Entity Framework (EF). Esta clase EF se usó luego en lafrom
clá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
IQueryable
Linq para Entidades. LaIEnumerable
tomó 15 segundos y laIQueryable
tomó 0.5 segundos en ejecutarse. Hubo tres tablas involucradas y, después de leer esto, creo que laIEnumerable
consulta 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.
fuente
IQueryable
s 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;)Me gustaría aclarar algunas cosas debido a respuestas aparentemente conflictivas (principalmente en torno a IEnumerable).
(1)
IQueryable
extiende laIEnumerable
interfaz. (Puede enviar un mensajeIQueryable
a algo que se esperaIEnumerable
sin error).(2) Ambos
IQueryable
yIEnumerable
LINQ 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,
IEnumerables
no son exclusivamente "en memoria".IQueryables
no siempre se ejecutan en la base de datos.IEnumerable
debe cargar cosas en la memoria (una vez recuperado, posiblemente de forma perezosa) porque no tiene un proveedor de datos abstractos.IQueryables
confí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
IQueryable
del contexto EF. (No hay registros en la memoria).(b) Pase el
IQueryable
a una vista cuyo modelo esIEnumerable
. (Válido. SeIQueryable
extiendeIEnumerable
)(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
IEnumerable
intentos 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
IEnumerable
unforeach( var record in resultSet )
bloque e intenta acceder simultáneamenterecord.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
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
Include
entidades explícitamente relacionadas de suDbSet
.fuente
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
fuente
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 #).fuente
Además de las primeras 2 respuestas realmente buenas (por driis y por Jacob):
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.
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.
fuente
Además de lo anterior, es interesante observar que puede obtener excepciones si usa en
IQueryable
lugar deIEnumerable
:Lo siguiente funciona bien si
products
es unIEnumerable
:Sin embargo, si
products
es unIQueryable
e intenta acceder a los registros de una tabla de base de datos, obtendrá este error:Esto se debe a que se creó la siguiente consulta:
y OFFSET no puede tener un valor negativo.
fuente