entity-framework-include

Obtener datos relacionados en Entity Framework con Include y ThenInclude

  • 4 min

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 del Cliente asociado a cada Pedido.
  • Esto se traduce en una sola consulta SQL que incluye un JOIN entre las tablas Pedidos y Clientes.

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.