martes, 21 de mayo de 2013
Sabemos que el uso descontrolado de la carga diferida o lazy loading puede echar abajo el rendimiento de nuestra aplicación, puesto que se generan peticiones al motor de datos al intentar recuperar entidades relacionadas cuando intentamos acceder a ellas. Sin embargo, no son pocos los casos en los que me encuentro que no se está usando apropiadamente, provocando un rendimiento terrible en el acceso a datos de las aplicaciones.
Imaginemos, por ejemplo, la siguiente estructura de datos, que podríamos describir diciendo que un usuario puede tener un número indeterminado de blogs, y en cada uno de ellos existirán un número indeterminado de posts:
E imaginemos ahora el siguiente código, que realiza un recorrido en profundidad de estos datos para mostrar los posts de cada blog de cada uno de los usuarios, generando la salida mostrada justo después:
Por defecto, con lazy loading activado, la ejecución de esta pequeña porción de código habría generado seis consultas a la base de datos:
La solución más frecuente es adelantar la carga de las entidades relacionadas, es decir, utilizar eager loading para traerse la estructura completa en una única consulta.
En una estructura como la que hemos visto pero con dos únicos niveles, sería algo así:
La fórmula a emplear en este caso es la siguiente:
Publicado en: Variable not found.
Imaginemos, por ejemplo, la siguiente estructura de datos, que podríamos describir diciendo que un usuario puede tener un número indeterminado de blogs, y en cada uno de ellos existirán un número indeterminado de posts:
E imaginemos ahora el siguiente código, que realiza un recorrido en profundidad de estos datos para mostrar los posts de cada blog de cada uno de los usuarios, generando la salida mostrada justo después:
using (var ctx = new MyDataContext()) { var query = ctx.Users; foreach (var person in query) { Console.WriteLine("User " + person.Name + ":"); foreach (var blog in person.Blogs) { Console.WriteLine(" Blog " + blog.Name + ":"); foreach (var post in blog.Posts) { Console.WriteLine(" Post: " + post.Title); } } } }
Por defecto, con lazy loading activado, la ejecución de esta pequeña porción de código habría generado seis consultas a la base de datos:
- Obtener todos los usuarios. Se obtienen dos instancias de
User
. - Obtener todos los blogs del primer usuario (jmaguilar). Se obtienen dos instancias de
Blog
. - Obtener todos los posts del primer blog de jmaguilar. Se obtienen dos instancias de
Post
. - Obtener todos los posts del segundo blog de jmaguilar. Se obtienen dos instancias de
Post
. - Obtener todos los blogs del segundo usuario (johnresig). Se obtiene una instancia de
Blog
. - Obtener todos los posts del único blog del usuario. Se obtiene una instancia de
Post
.
La solución más frecuente es adelantar la carga de las entidades relacionadas, es decir, utilizar eager loading para traerse la estructura completa en una única consulta.
En una estructura como la que hemos visto pero con dos únicos niveles, sería algo así:
using (var ctx = new MyDataContext()) { var query = ctx.Users.Include(u => u.Blogs); foreach (var person in query) { // Code omitted } }La cláusula
Include()
indicará a Entity Framework que cuando realice la consulta a la colección de usuarios debe traerse también la colección de blogs relacionados con cada uno de ellos. Si ejecutamos el ejemplo anterior, podremos comprobar que el número de consultas se ha reducido a cuatro:- Obtener todos los usuarios junto con sus blogs.
- Obtener todos los posts del primer blog del usuario.
- Obtener todos los posts del segundo blog del usuario jmaguilar.
- Obtener todos los posts del blog del usuario johnresig.
Post
asociados con los blogs. Sin embargo, la forma de hacerlo no es tan obvia ni intuitiva como la anterior. No podemos especificarlo en el Include()
, puesto que no es posible referenciar la colección como u.Blogs.Posts
, dado que Posts
no es una propiedad de la colección Blogs
del usuario y fallaría en compilación.La fórmula a emplear en este caso es la siguiente:
using (var ctx = new MyDataContext()) { var query = ctx.Users.Include(u => u.Blogs.Select(b => b.Posts)); foreach (var person in query) { // Code omitted } }Y de esta forma tan simple estaremos obteniendo la estructura de datos completa realizando una única consulta a la base de datos, con el beneficio de rendimiento que esto conlleva.
Publicado en: Variable not found.
6 Comentarios:
Hola José María,
La verdad es que ambas cosas tienen pros y cons, por ejemplo:
Lazy Loading:
Pros:
-Bajo consumo de memoria
-Queries más rápidas
Cons:
-Mutiple roundtrips
Eager Loading:
Pros:
-Con un roundtrip podemos traernos todos los datos
Cons:
-Mayor consumo de memoria
-Queries más complicadas (Multiples joins)
Yo creo que lo bueno es detectar cuando es buena una cosa y cuando otra, pero como siempre en este mundo no hay balas de plata.
Buen artículo maestro.
Hola, amigo!
Efectivamente, no hay balas de plata. Lo importante es saber de antemano qué uso se va a dar a la información de una consulta y optar por el enfoque más apropiado.
El problema es a veces el no saber que existen distintas opciones, y usar siempre la misma (ya sea eager o lazy).
Gracias por tu aportación!
Un abrazo
Hola Jose Maria,
Como siempre dando en el clavo, hay que tener muy en cuenta esto, porque hace que la carga de unos datos puedan bajar de 100 segundos a uno, hace relativamente poco tiempo encontre un helper en System.Data.Entity.DbExtensions Include que permite anidar includesy de esta manera podia hacer consultas mas complejas para cargar datos de una vez evitandome el lazy load, claro todo a su medida y cuando sea necesario.
Saludos y gracias por mantenerte activo
Hola,
Pero Include es un método cuyo parámetro de entrada es un string.
Estoy utilizando EF 5.
Gracias
Es que hay que hacer un using de:
using System.Data.Entity;
En caso contrario solo aparece el método Include con parámetro de entrada string
Eso es :)
Un saludo y gracias por comentarlo.
Enviar un nuevo comentario