jueves, 5 de diciembre de 2013

Los IEnumerables

Hoy quiero escribir acerca de los IEnumerables debido a un problema de queries duplicados detectado por miniprofiler que tuve en uno de mis proyectos.

Nosotros estamos acostumbrados a trabajar con funciones que nos devuelven un IEnumerable con datos de la base de datos:
IEnumerable<Order> GetOrders(int take = 10, int skip = 0);

Supongamos que nuestra implementación de GetOrders es algo como:
public IEnumerable<Order> GetOrders(int take = 10, int skip = 0)
{
    return this.FindAll()
        .OrderByDescending(o => o.ID)
        .Skip(skip)
        .Take(take)
        .AsEnumerable();
}

La tendencia natural es suponer que cuando hacemos una llamada al procedimiento GetOrders obtenemos un objeto tipo lista que podemos iterar desde cualquier punto de nuestro código sin ningún problema... y realmente es así, pero tenemos que estar conscientes de que cada vez que iteramos sobre dicho objeto se realizará una nueva consulta en la base de datos.
var orders = orderServices.GetOrders();
// Hasta ahora no hay consultas a base de datos

var ordersList = orders.ToList();
// Se realizó una consulta en base de datos

foreach (var order in orders) {...}
// Se realizó nuevamente una consulta en base de datos

Y así sucesivamente, cada vez que "enumeramos" sobre el objeto "orders" de tipo IEnumerable estaremos realizando una consulta en la base de datos que quizás no era lo que esperábamos.

Lo mas recomendable, si vamos a usar un objeto de tipo IEnumerable que necesitamos iterar mas de una vez, es crear una lista que almacena sus datos en memoria y pueda ser iterada las veces que se necesite sin realizar consultas innecesarias a la base de datos. En el ejemplo anterior podemos utilizar "ordersList" en lugar de "orders" cuando hacemos la iteración "foreach", el resultado será el mismo pero de esta manera se evita realizar la consulta extra a la base de datos.

Otro problema que puede ser un poco mas difícil de detectar es cuando hacemos una llamada a un procedimiento que tiene un parámetro de tipo IEnumerable. Digo "difícil de detectar" porque en la mayoría de los casos se nos puede pasar el hecho de que ese procedimiento va a "enumerar" nuestro objeto (consultando a la base de datos) y nosotros, inocentemente, lo volvemos a enumerar en nuestro código, y por supuesto, generando una consulta duplicada a base de datos. El otro problema es que le estamos dando la exclusividad a dicho procedimiento de utilizar nuestro objeto IEnumerable sin que nosotros lo podamos enumerar en nuestro código (claro si no queremos hacer consultas duplicadas a base de datos). Lo que se recomienda en estos casos es crear procedimientos con otro tipo de parámetros distintos a IEnumerable, por ejemplo List, así estaremos pasando una lista en memoria que puede ser leída múltiples veces sin consultar a la base de datos.

Según tengo entendido, no estoy 100% seguro porque no utilizo Resharper, es que las últimas versiones de Resharper pueden detectar este tipo de problemas con los IEnumerables y da una advertencia: "Possible Multiple Enumeration of IEnumerable".

Con Miniprofiler se pueden observar las consultas que Entity Framework hace en la base de datos. Miniprofiler nos puede dar una advertencia en caso de que se hagan consultas duplicadas y también nos muestra en qué lugar de nuestro código se realizan las consultas a la base de datos, como se puede ver en la siguiente imagen, aquí se efectuaron 2 consultas en el Controller y una en el Render (o Vista).


Por último les invito a probar Miniprofiler y a jugar con enumerar los objetos tanto en la Vista como en el Controller para comparar los resultados.

No hay comentarios:

Publicar un comentario