Carga diligente de datos relacionados

Carga diligente

Puede usar el método Include para especificar los datos relacionados que se incluirán en los resultados de la consulta. En el ejemplo siguiente, las entradas relacionadas rellenarán la propiedad Posts de los blogs que se devuelvan en los resultados.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .ToList();
}

Sugerencia

Entity Framework Core corregirá automáticamente las propiedades de navegación para todas las entidades que se cargaron previamente en la instancia del contexto. Por tanto, incluso si los datos de una propiedad de navegación no se incluyen explícitamente, es posible que la propiedad se siga rellenando si algunas o todas las entidades relacionadas se cargaron previamente.

Puede incluir los datos relacionados de varias relaciones en una sola consulta.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .Include(blog => blog.Owner)
        .ToList();
}

Precaución

La carga diligente de navegación de una colección en una sola consulta puede producir problemas de rendimiento. Para obtener más información, vea Consultas únicas frente a consultas divididas.

Inclusión de varios niveles

Puede explorar en profundidad las relaciones para incluir varios niveles de datos relacionados con el método ThenInclude. En el ejemplo siguiente se cargan todos los blogs, las entradas relacionadas y el creador de cada entrada.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author)
        .ToList();
}

Puede encadenar varias llamadas en ThenInclude para continuar incluyendo más niveles de datos relacionados.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author)
        .ThenInclude(author => author.Photo)
        .ToList();
}

Puede combinar todas las llamadas para incluir datos relacionados provenientes de varios niveles y varias raíces en la misma consulta.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author)
        .ThenInclude(author => author.Photo)
        .Include(blog => blog.Owner)
        .ThenInclude(owner => owner.Photo)
        .ToList();
}

Es posible que quiera incluir varias entidades relacionadas para una de las entidades que se está incluyendo. Por ejemplo, cuando consulte Blogs, incluye Posts y luego quiere incluir tanto Author como Tags de las Posts. Para ello, debe especificar cada inicio de ruta de acceso de inclusión en la raíz. Por ejemplo, Blog -> Posts -> Author y Blog -> Posts -> Tags. Esto no significa que vaya a obtener combinaciones redundantes; en la mayoría de los casos, EF mezclará las combinaciones al generar código SQL.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Tags)
        .ToList();
}

Sugerencia

También puede cargar varias navegaciones mediante un único método Include. Esto es posible para las "cadenas" de navegación que son todas las referencias, o cuando terminan con una sola colección.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Owner.AuthoredPosts)
        .ThenInclude(post => post.Blog.Owner.Photo)
        .ToList();
}

Inclusión filtrada

Al aplicar la inclusión para cargar datos relacionados, puede agregar determinadas operaciones enumerables en la navegación de colección incluida, lo que permite filtrar y ordenar los resultados.

Las operaciones que se admiten son: Where, OrderBy, OrderByDescending, ThenBy, ThenByDescending, Skip y Take.

Dichas operaciones se deben aplicar en la navegación de colección en la expresión lambda que se pasa al método Include, como se muestra en el ejemplo siguiente:

using (var context = new BloggingContext())
{
    var filteredBlogs = context.Blogs
        .Include(
            blog => blog.Posts
                .Where(post => post.BlogId == 1)
                .OrderByDescending(post => post.Title)
                .Take(5))
        .ToList();
}

Cada navegación incluida solo permite un único conjunto de operaciones de filtro. En los casos en los que se aplican varias operaciones Include para una navegación de colección determinada (blog.Posts en los ejemplos siguientes), las operaciones de filtro solo se pueden especificar en una de ellas:

using (var context = new BloggingContext())
{
    var filteredBlogs = context.Blogs
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
        .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Tags.OrderBy(postTag => postTag.TagId).Skip(3))
        .ToList();
}

En lugar de eso, se pueden aplicar operaciones idénticas para cada navegación que esté incluida varias veces:

using (var context = new BloggingContext())
{
    var filteredBlogs = context.Blogs
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
        .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
        .ThenInclude(post => post.Tags.OrderBy(postTag => postTag.TagId).Skip(3))
        .ToList();
}

Precaución

En el caso de las consultas de seguimiento, los resultados de Inclusión filtrada pueden ser inesperados debido a la corrección de la navegación. Todas las entidades pertinentes que se hayan consultado anteriormente y que se hayan almacenado en el seguimiento de cambios estarán presentes en los resultados de la consulta Include filtrada aunque no cumplan los requisitos del filtro. Valore la posibilidad de usar consultas NoTracking o de volver a crear el elemento DbContext al emplear Inclusión filtrada en esas situaciones.

Ejemplo:

var orders = context.Orders.Where(o => o.Id > 1000).ToList();

// customer entities will have references to all orders where Id > 1000, rather than > 5000
var filtered = context.Customers.Include(c => c.Orders.Where(o => o.Id > 5000)).ToList();

Nota:

En el caso de las consultas de seguimiento, se considera que se ha cargado la navegación en la que se aplicó la inclusión filtrada. Esto significa que EF Core no intentará volver a cargar sus valores mediante la carga explícita o la carga diferida, aunque aún falten algunos elementos.

Inclusión en tipos derivados

Puede incluir datos relacionados provenientes de la navegación que se define solo en un tipo derivado con Include y ThenInclude.

Dado el modelo siguiente:

public class SchoolContext : DbContext
{
    public DbSet<Person> People { get; set; }
    public DbSet<School> Schools { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<School>().HasMany(s => s.Students).WithOne(s => s.School);
    }
}

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Student : Person
{
    public School School { get; set; }
}

public class School
{
    public int Id { get; set; }
    public string Name { get; set; }

    public List<Student> Students { get; set; }
}

El contenido de la navegación School de todas las personas que son estudiantes se puede cargar de manera diligente usando muchos patrones:

  • Uso de conversión

    context.People.Include(person => ((Student)person).School).ToList()
    
  • Uso del operador as

    context.People.Include(person => (person as Student).School).ToList()
    
  • Uso de la sobrecarga de Include que toma el parámetro del tipo string

    context.People.Include("School").ToList()
    

Configuración de modelos para navegaciones de inclusión automática

Puede configurar una navegación en el modelo para que se incluya cada vez que la entidad se carga desde la base de datos mediante el método AutoInclude. Tiene el mismo efecto que especificar Include con la navegación en cada consulta en la que el tipo de entidad se devuelva en los resultados. En el ejemplo siguiente se muestra cómo configurar una navegación que se pueda incluir automáticamente.

modelBuilder.Entity<Theme>().Navigation(e => e.ColorScheme).AutoInclude();

Después de la configuración de más arriba, si se ejecuta una consulta como se indica a continuación, se cargará la navegación ColorScheme para todos los temas de los resultados.

using (var context = new BloggingContext())
{
    var themes = context.Themes.ToList();
}

Esta configuración se aplica a todas las entidades devueltas en el resultado, independientemente de cómo apareciera en los resultados. Por lo tanto, si una entidad está en el resultado debido al uso de una navegación, al usar Include en otro tipo de entidad o una configuración de inclusión automática, se cargarán todas las navegaciones de inclusión automática para esta. La misma regla se extiende a las navegaciones configuradas como de inclusión automática en el tipo derivado de la entidad.

Si para una consulta concreta no quiere cargar los datos relacionados mediante una navegación, que se configura en el nivel de modelo que se debe incluir automáticamente, puede usar el método IgnoreAutoIncludes en su consulta. Al usar este método, se dejarán de cargar todas las navegaciones configuradas como de inclusión automática por el usuario. si se ejecuta una consulta como se indica a continuación, se recuperarán todos los temas de la base de datos, pero no se cargará ColorScheme aunque se haya configurado como una navegación de inclusión automática.

using (var context = new BloggingContext())
{
    var themes = context.Themes.IgnoreAutoIncludes().ToList();
}

Nota:

Las navegaciones a tipos de propiedad también se configuran como de inclusión automática por convención, y usar la API IgnoreAutoIncludes no hace que se detenga su inclusión. Se incluirán igualmente en los resultados de la consulta.