Pregunta Devolver IEnumerable frente a IQueryable


Cuál es la diferencia entre regresar IQueryable<T> vs. IEnumerable<T>?

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;

¿Ambos serán ejecución diferida y cuándo se debe preferir uno sobre el otro?


898
2018-05-20 18:13


origen


Respuestas:


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

La diferencia es que IQueryable<T> es la interfaz que permite que LINQ-to-SQL (LINQ.-a-cualquier cosa) 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 de la base de datos.

En codigo:

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

Ese código ejecutará SQL para solo seleccionar clientes de oro. El siguiente código, por otro lado, ejecutará la consulta original en la base de datos, luego filtrará a 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 trabajando en IQueryable<T> en muchos casos, puede evitar que regrese demasiadas filas de la base de datos. Otro buen ejemplo es hacer paginación: si usa Take y Skip en IQueryable, solo obtendrá el número de filas solicitadas; haciendo eso en una IEnumerable<T> hará que todas sus filas se carguen en la memoria.


1522
2018-05-20 18:19



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

  • los IEnumerable la firma de la versión es: Where(Func<Customer, bool> predicate)

  • los IQueryable la firma de la versión es: Where(Expression<Func<Customer, bool>> predicate)

Probablemente haya estado usando ambos sin darse cuenta porque ambos se invocan usando una sintaxis idéntica:

p.ej. Where(x => x.City == "<City>") funciona en ambos IEnumerable y IQueryable

  • Cuando usas Where() en una IEnumerable colección, el compilador pasa una función compilada a Where()

  • Cuando usas Where() en una IQueryable colecció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 que es fácilmente digerible.

¿Por qué molestarse con este árbol de expresiones? sólo quiero Where() para filtrar mis datos La razón principal es que tanto EF como los ORM 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 libre, debería usar AsQueryable() por todo el lugar en ese caso?  No, IQueryable solo es útil si el proveedor de datos subyacente puede hacer algo con él. Convirtiendo algo así como un regular List a IQueryable no te dará ningún beneficio.


195
2018-02-14 08:00



Sí, ambos usan ejecución diferida. Vamos a ilustrar la diferencia usando el perfilador 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]"

Aproximadamente toma 90 segundos ejecutar ese bloque de código contra una tabla WebLog que tiene 1 millón de registros.

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

Cuando usamos IQueryable en lugar de IEnumerable en 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"

Aproximadamente toma cuatro segundos 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 usamos el result en nuestro ejemplo (que se denomina ejecución diferida), y al final esta expresión se convertirá en una consulta SQL para que se ejecute en el motor de la base de datos.


56
2018-06-08 01:11



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

En cuanto a cuál se prefiere sobre el otro, depende de cuál es el origen de datos subyacente.

Devolver un IEnumerable forzará automáticamente el tiempo de ejecución para usar LINQ en Objetos para consultar su colección.

Devolver un IQueryable (que implementa IEnumerable, por cierto) proporciona la funcionalidad adicional para traducir su consulta en algo que pueda tener un mejor rendimiento en el origen subyacente (LINQ to SQL, LINQ to XML, etc.).


54
2018-05-20 18:18



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

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 comienzas con un IQueryable<>, por lo general, desea mantenerlo como una IQueryable<> hasta que haya alguna razón convincente para cambiarlo. La razón de esto es que desea darle al procesador de consultas la mayor cantidad de información posible. Por ejemplo, si solo va a usar 10 resultados (ha llamado Take(10)) luego 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<> en su objeto particular, o no puede manejar o maneja de manera ineficiente. En ese caso, es posible que desee convertir el tipo a IEnumerable<> (asignando una variable de tipo IEnumerable<> o usando el AsEnumerable método de extensión, por ejemplo) para que las funciones de extensión que usted llama terminen siendo las que están en el Enumerable clase en lugar de la Queryable clase.


23
2018-05-15 00:00



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

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

Si desea transportar solo un conjunto de objetos para enumerar, simplemente tome IEnumerable.

Imagine un IQueryable como ese que es, una "consulta" de datos (que puede refinar si lo desea)

Un IEnumerable es un conjunto de objetos (que ya se ha recibido o se ha creado) sobre los que puede enumerar.


21
2018-04-11 13:09



Hay una publicación en el 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 las consultas de LINQ: Marco de la entidad: IQueryable vs. IEnumerable.

Si profundizamos más y observamos las fuentes, podemos ver que obviamente hay diferentes métodos de extensión para los que se realizan 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 IQueryable fuente.


17
2018-04-23 22:45