IEnumerable<T> vs IQueryable<T>: ¿qué elegir en C#? Análisis completo

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

IEnumerable<T> vs IQueryable<T>: el duelo que decidirá el destino de tu aplicación

\n\n

¿Alguna vez te has preguntado por qué tu consulta a la base de datos va lenta, aunque los datos sean pocos? ¿O por qué después de agregar una línea de código la aplicación se vuelve una tortuga? Lo más probable es que te hayas enfrentado al clásico dilema: IEnumerable<T> o IQueryable<T>? No son solo dos interfaces del mundo de C# y LINQ. Son dos mundos diferentes, dos enfoques para trabajar con datos. Uno es para colecciones locales, el otro para fuentes remotas, como bases de datos. En este artículo no solo enumeraremos las diferencias, sino que las analizaremos con ejemplos reales, para que nunca más caigas en la trampa del rendimiento.

\n\n

¿Quién es quién? Conceptos básicos

\n\n

Empecemos por lo básico. IEnumerable<T> es una interfaz que representa una secuencia de elementos en memoria. Es ideal para trabajar con colecciones como List<T>, Array o Dictionary. Cuando usas IEnumerable, todas las operaciones (filtrado, ordenamiento) se ejecutan del lado del cliente, es decir, en tu aplicación.

\n\n

IQueryable<T> es una interfaz más avanzada, diseñada para trabajar con fuentes de datos externas (bases de datos, servicios web). No almacena datos, sino que construye un árbol de expresiones (Expression Tree). Esto permite que el proveedor de LINQ (por ejemplo, Entity Framework) convierta tu código C# en una consulta SQL eficiente y la ejecute del lado del servidor.

\n\n

La frase clave que debes recordar: IEnumerable trabaja en memoria, IQueryable trabaja del lado de la fuente de datos.

\n\n

La diferencia principal: dónde y cuándo se ejecuta la lógica

\n\n

Imagina que necesitas filtrar una lista de 10 000 productos que cuesten más de 1000 rublos y ordenarlos por precio.

\n\n

Ejemplo 1: IEnumerable (filtrado en memoria)

\n
// Supongamos que products es un List<Product> (IEnumerable)\nvar expensiveProducts = products\n    .Where(p => p.Price > 1000)\n    .OrderBy(p => p.Price)\n    .ToList();\n
\n

¿Qué sucede aquí? Primero, se cargan TODOS los 10 000 productos en memoria. Luego, C# aplica el filtro Where y la ordenación OrderBy en tu aplicación. Si tienes 10 000 registros, no es crítico. Pero si son 10 millones, la aplicación se caerá o se congelará.

\n\n

Ejemplo 2: IQueryable (filtrado en el servidor de BD)

\n
// Supongamos que dbContext.Products es un DbSet<Product> (IQueryable)\nvar expensiveProducts = dbContext.Products\n    .Where(p => p.Price > 1000)\n    .OrderBy(p => p.Price)\n    .ToList();\n
\n

Aquí está la magia: IQueryable no carga los datos de inmediato. En su lugar, construye una consulta SQL: SELECT * FROM Products WHERE Price > 1000 ORDER BY Price. Esta consulta se envía a la base de datos, y esta devuelve solo los datos filtrados y ordenados. En memoria obtienes solo lo que necesitas. El ahorro de recursos es enorme.

\n\n

Ejemplo práctico: la trampa de ToList()

\n\n

Uno de los errores más comunes de los desarrolladores principiantes es llamar prematuramente a ToList() o ToArray(). Esto convierte IQueryable en IEnumerable.

\n\n

Código malo (mata el rendimiento):

\n
var allProducts = dbContext.Products.ToList(); // ¡Cargamos TODOS los datos en memoria!\nvar filteredProducts = allProducts.Where(p => p.Price > 1000).ToList();\n
\n

En este ejemplo, primero cargamos toda la tabla Products en memoria, y luego la filtramos. Es equivalente a descargar todo el almacén en el estacionamiento y luego buscar la caja correcta entre el montón.

\n\n

Código bueno (óptimo):

Blogs

Book Recommendations