c# - soporta - ¿Foreach ejecuta la consulta solo una vez?
optimizar consultas lentas mysql (4)
Tengo una lista de artículos y una consulta LINQ sobre ellos. Ahora, con la ejecución diferida de LINQ, ¿un bucle foreach subsiguiente ejecutará la consulta solo una vez o por cada vuelta en el bucle?
Dado este ejemplo (tomado de Introducción a las consultas LINQ (C #), en MSDN )
// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery =
from num in numbers
where (num % 2) == 0
select num;
// 3. Query execution.
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}
O, en otras palabras, habría alguna diferencia si tuviera :
foreach (int num in numQuery.ToList())
Y, ¿importaría, si los datos subyacentes no están en una matriz, sino en una base de datos?
Ahora, con la ejecución diferida de LINQ, ¿un bucle foreach subsiguiente ejecutará la consulta solo una vez o por cada vuelta en el bucle?
Sí, una vez para el bucle. En realidad, puede ejecutar la consulta menos de una vez; podría abortar el ciclo de manera parcial y la prueba (num % 2) == 0
no se realizaría en ningún elemento restante.
O, en otras palabras, habría alguna diferencia si tuviera:
foreach (int num in numQuery.ToList())
Dos diferencias:
En el caso anterior,
ToList()
pierde tiempo y memoria, porque primero hace lo mismo queforeach
inicial, construye una lista a partir de ella, y luegoforeach
esa lista. Las diferencias estarán en algún lugar entre trivial e impedir que el código funcione, dependiendo del tamaño de los resultados.Sin embargo, en el caso en el que vaya a hacer repetidamente
foreach
con los mismos resultados, o si no lo usará repetidamente, mientras queforeach
solo ejecuta la consulta una vez, el siguienteforeach
ejecuta nuevamente. Si la consulta es costosa, entonces el enfoqueToList()
(y el almacenamiento de esa lista) puede ser un ahorro masivo.
Como está escrito, cada iteración del bucle haría exactamente el trabajo que fuera necesario para obtener el siguiente resultado. Así que la respuesta sería técnicamente "ninguna de las anteriores". La consulta se ejecutaría "en piezas".
Si usa ToList()
o cualquier otro método de materialización ( ToArray()
etc.), la consulta se evaluará una vez en el momento y las operaciones subsiguientes (como la iteración de los resultados) simplemente funcionarán en una lista "simple".
Si los numbers
fueran un IQueryable
lugar de un IEnumerable
(como sería en un escenario de base de datos), lo anterior aún está cerca de la verdad, aunque no es una descripción perfectamente precisa. En particular, en el primer intento de materializar un resultado, el proveedor consultable hablaría con la base de datos y produciría un conjunto de resultados; luego, las filas de este conjunto de resultados se extraerían en cada iteración.
La consulta linq se ejecutará cuando se enumere (ya sea como resultado de una llamada .ToList()
o haciendo un foreach
sobre los resultados.
Si está enumerando los resultados de la consulta linq dos veces, ambas veces provocarán que consulte la fuente de datos (en su ejemplo, la enumeración de la colección) ya que solo devuelve un IEnumerable
. Sin embargo, dependiendo de la consulta de linq, es posible que no siempre enumere toda la colección (por ejemplo, .Any()
y .Single()
se detendrán en el primer objeto o el primer objeto coincidente si hay un .Where()
).
Los detalles de implementación de un proveedor de linq pueden diferir, por lo que el comportamiento habitual cuando el origen de datos es una base de datos es llamar a .ToList()
inmediatamente para cache
en cache
los resultados de la consulta y también garantizar que la consulta (en el caso de EF, L2S o NHibernate) se ejecuta una vez allí y luego en lugar de cuando la colección se enumera en algún punto posterior en el código y para evitar que la consulta se ejecute varias veces si los resultados se enumeran varias veces.
No, no hace ninguna diferencia. La expresión se evalúa una vez. Más específicamente, la construcción foreach
invoca el método GetEnumerator()
en la expresión in
y llama repetidamente MoveNext()
y accede a la propiedad Current
para atravesar el IEnumerable
.
OTOH, llamar a ToList()
es redundante. No deberías molestarte en llamarlo.
Si la entrada es una base de datos, la situación es ligeramente diferente, ya que LINQ genera IQueryable
, pero estoy bastante seguro de que foreach
aún lo trata como un IEnumerable
(que IQueryable
hereda).