Al trabajar con Entity Framework, uno de sus puntos fuertes es la capacidad de cargar datos relacionados entre entidades.
Cuando trabajamos con bases de datos relacionales, es común que las entidades estén relacionadas entre sí (por algo se llaman “relacionales” 🤗).
Por ejemplo, una entidad Pedido
puede estar relacionada con una entidad Cliente
, y a su vez, un Cliente
puede tener múltiples Pedidos
.
Cuando realizamos una consulta para obtener un Pedido
, es posible que también necesitemos información sobre el Cliente
asociado.
Entity Framework permite cargar estos datos relacionados de manera de forma sencilla utilizando los métodos Include
y ThenInclude
.
Vamos a ver cada uno de estos métodos 👇.
Uso de Include
El método Include
se utiliza para cargar datos relacionados de una entidad en una sola consulta. Su sintaxis es sencilla:
var pedidos = context.Pedidos
.Include(p => p.Cliente)
.ToList();
En este ejemplo,
- Estamos obteniendo todos los
Pedidos
y, además, cargamos la información delCliente
asociado a cadaPedido
. - Esto se traduce en una sola consulta SQL que incluye un
JOIN
entre las tablasPedidos
yClientes
.
Ejemplo práctico
Imaginemos que tenemos las siguientes entidades:
public class Cliente
{
public int ClienteId { get; set; }
public string Nombre { get; set; }
public ICollection<Pedido> Pedidos { get; set; }
}
public class Pedido
{
public int PedidoId { get; set; }
public DateTime Fecha { get; set; }
public int ClienteId { get; set; }
public Cliente Cliente { get; set; }
}
Si queremos obtener todos los Pedidos
junto con la información del Cliente
asociado, podemos hacerlo de la siguiente manera:
var pedidos = context.Pedidos
.Include(p => p.Cliente)
.ToList();
Esto generará una consulta SQL similar a:
SELECT p.*, c.*
FROM Pedidos p
INNER JOIN Clientes c ON p.ClienteId = c.ClienteId;
Carga de múltiples relaciones
En algunos casos, es posible que necesites cargar múltiples relaciones en una sola consulta. Por ejemplo, si un Pedido
tiene un Cliente
y un Producto
, puedes cargar ambas relaciones de la siguiente manera:
var pedidos = context.Pedidos
.Include(p => p.Cliente)
.Include(p => p.Producto)
.ToList();
Carga condicional con relaciones
Por supuesto, podemos realizar cargas condicionales aplicando Where
después de Include
, aplicando una condición a la tabla relacional. Es decir, puedes hacer,
var pedidos = context.Pedidos
.Include(p => p.Cliente)
.Where(p => p.Fecha.Year == 2023)
.ToList();
Uso de relaciones anidadas con ThenInclude
En algunos casos, las relaciones entre entidades son más complejas y pueden involucrar múltiples niveles. Para carga relacionados anidados, utilizamos el método ThenInclude
.
- Include realiza relaciones
join
con la tabla de primer nivel - ThenInclude realiza relaciones
join
posteriores, con las tablas cargadas previamente
Ejemplo práctico
Por ejemplo, un Pedido
puede estar relacionado con un Cliente
, y este a su vez tener una Direccion
.
Supongamos que tenemos una entidad Direccion
relacionada con Cliente
:
public class Direccion
{
public int DireccionId { get; set; }
public string Calle { get; set; }
public string Ciudad { get; set; }
public int ClienteId { get; set; }
public Cliente Cliente { get; set; }
}
Si queremos obtener todos los Pedidos
junto con la información del Cliente
y sus Direcciones
, podemos hacerlo de la siguiente manera:
var pedidos = context.Pedidos
.Include(p => p.Cliente)
.ThenInclude(c => c.Direcciones)
.ToList();
Esto generará una consulta SQL que incluye un JOIN
entre las tablas Pedidos
, Clientes
y Direcciones
.
Consideraciones de rendimiento
Las relaciones, y como trabajar con ellas, siempre han sido uno de los mayores creadores de problemas de rendimiento potenciales al trabajar con bases de datos.
Con Entity Framework, al igual que con cualquier otra tecnología para conectarse con base de datos, también tenemos que estar atentos a esto.
Include
realiza una carga eager (carga inmediata) de los datos relacionados. Es decir, que se baja los datos, y todos los datos relacionados de una vez.
Esto evita el problema denomiado como N+1 queries, que significa que se realizan múltiples consultas a la base de datos para obtener datos relacionados (lo que puede afectar significativamente el rendimiento).
Pero, por otro lado, también puedes acabar descargando muchísimos datos. Lo cual también resulta en generar una consulta SQL enorme y lenta.
En algunos casos, puede ser más eficiente utilizar carga lazy (carga diferida) o carga explícita, dependiendo del escenario.
En general, como regla de oro descarga todo lo que necesites, y sólo lo que necesites, en el menor número de consultas posibles al trabajar con base de datos.