Carga de datos relacionadosLoading Related Data

Entity Framework Core permite usar las propiedades de navegación del modelo para cargar las entidades relacionados.Entity Framework Core allows you to use the navigation properties in your model to load related entities. Existen tres patrones de O/RM comunes que se usan para cargar los datos relacionados.There are three common O/RM patterns used to load related data.

  • Carga diligente significa que los datos relacionados se cargan desde la base de datos como parte de la consulta inicial.Eager loading means that the related data is loaded from the database as part of the initial query.
  • Carga explícita significa que los datos relacionados se cargan de manera explícita desde la base de datos más adelante.Explicit loading means that the related data is explicitly loaded from the database at a later time.
  • Carga diferida significa que los datos relacionados se cargan de manera transparente desde la base de datos cuando se accede a la propiedad de navegación.Lazy loading means that the related data is transparently loaded from the database when the navigation property is accessed.

Sugerencia

Puede ver un ejemplo de este artículo en GitHub.You can view this article's sample on GitHub.

Carga diligenteEager loading

Puede usar el método Include para especificar los datos relacionados que se incluirán en los resultados de la consulta.You can use the Include method to specify related data to be included in query results. En el ejemplo siguiente, las entradas relacionadas rellenarán la propiedad Posts de los blogs que se devuelvan en los resultados.In the following example, the blogs that are returned in the results will have their Posts property populated with the related posts.

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.Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. 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.So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.

Puede incluir los datos relacionados de varias relaciones en una sola consulta.You can include related data from multiple relationships in a single query.

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

Inclusión de varios nivelesIncluding multiple levels

Puede explorar en profundidad las relaciones para incluir varios niveles de datos relacionados con el método ThenInclude.You can drill down through relationships to include multiple levels of related data using the ThenInclude method. En el ejemplo siguiente se cargan todos los blogs, las entradas relacionadas y el creador de cada entrada.The following example loads all blogs, their related posts, and the author of each post.

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.You can chain multiple calls to ThenInclude to continue including further levels of related data.

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

Puede combinar todo esto para incluir datos relacionados provenientes de varios niveles y varias raíces en la misma consulta.You can combine all of this to include related data from multiple levels and multiple roots in the same query.

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.You may want to include multiple related entities for one of the entities that is being included. Por ejemplo, cuando consulte Blogs, incluye Posts y luego quiere incluir tanto Author como Tags de las Posts.For example, when querying Blogs, you include Posts and then want to include both the Author and Tags of the Posts. Para hacerlo, debe especificar cada inicio de ruta de acceso de inclusión en la raíz.To do this, you need to specify each include path starting at the root. Por ejemplo, Blog -> Posts -> Author y Blog -> Posts -> Tags.For example, Blog -> Posts -> Author and Blog -> Posts -> Tags. Esto no significa que vaya a obtener combinaciones redundantes; en la mayoría de los casos, EF consolidará las combinaciones al generar código SQL.This does not mean you will get redundant joins; in most cases, EF will consolidate the joins when generating 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();
}

Precaución

Desde la versión 3.0.0, todas las instancias de Include producirán que se agregue una combinación JOIN adicional a las consultas SQL generadas por los proveedores relacionales, mientras que las versiones anteriores generaban consultas SQL adicionales.Since version 3.0.0, each Include will cause an additional JOIN to be added to SQL queries produced by relational providers, whereas previous versions generated additional SQL queries. Esto puede cambiar significativamente el rendimiento de las consultas, tanto para bien como para mal.This can significantly change the performance of your queries, for better or worse. En concreto, es posible que las consultas LINQ con un número excesivamente alto de operadores Include deban dividirse en varias consultas LINQ independientes con el fin de evitar el problema de explosión cartesiana.In particular, LINQ queries with an exceedingly high number of Include operators may need to be broken down into multiple separate LINQ queries in order to avoid the cartesian explosion problem.

Inclusión filtradaFiltered include

Nota

Esta característica se incluye por primera vez en EF Core 5.0.This feature is introduced in EF Core 5.0.

Al aplicar Include para cargar datos relacionados, puede aplicar determinadas operaciones enumerables en la navegación de colección incluida, lo que permite filtrar y ordenar los resultados.When applying Include to load related data, you can apply certain enumerable operations on the included collection navigation, which allows for filtering and sorting of the results.

Las operaciones que se admiten son: Where, OrderBy, OrderByDescending, ThenBy, ThenByDescending, Skip y Take.Supported operations are: Where, OrderBy, OrderByDescending, ThenBy, ThenByDescending, Skip, and 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:Such operations should be applied on the collection navigation in the lambda passed to the Include method, as shown in example below:

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.Each included navigation allows only one unique set of filter operations. 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:In cases where multiple Include operations are applied for a given collection navigation (blog.Posts in the examples below), filter operations can only be specified on one of them:

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();
}

De manera alternativa, se pueden aplicar operaciones idénticas para cada navegación que esté incluida varias veces:Alternatively, identical operations can be applied for each navigation that is included multiple times:

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();
}

Inclusión en tipos derivadosInclude on derived types

Puede incluir datos relacionados provenientes de las navegaciones que se definen solo en un tipo derivado con Include y ThenInclude.You can include related data from navigations defined only on a derived type using Include and ThenInclude.

Dado el modelo siguiente:Given the following model:

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 mediante el uso de diversos patrones:Contents of School navigation of all People who are Students can be eagerly loaded using a number of patterns:

  • con conversiónusing cast

    context.People.Include(person => ((Student)person).School).ToList()
    
  • con el operador asusing as operator

    context.People.Include(person => (person as Student).School).ToList()
    
  • con la sobrecarga de Include que toma el parámetro del tipo stringusing overload of Include that takes parameter of type string

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

Carga explícitaExplicit loading

Puede cargar de manera explícita una propiedad de navegación a través de la API DbContext.Entry(...).You can explicitly load a navigation property via the DbContext.Entry(...) API.

using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);

    context.Entry(blog)
        .Collection(b => b.Posts)
        .Load();

    context.Entry(blog)
        .Reference(b => b.Owner)
        .Load();
}

También puede cargar de manera explícita una propiedad de navegación si ejecuta una consulta independiente que devuelve las entidades relacionadas.You can also explicitly load a navigation property by executing a separate query that returns the related entities. Si está habilitado el seguimiento de cambios, cuando se cargue una entidad, EF Core establecerá automáticamente las propiedades de navegación de la entidad recién cargada para hacer referencia a cualquier entidad ya cargada y establecerá las propiedades de navegación de las entidades ya cargadas para hacer referencia a la entidad recién cargada.If change tracking is enabled, then when loading an entity, EF Core will automatically set the navigation properties of the newly-loaded entitiy to refer to any entities already loaded, and set the navigation properties of the already-loaded entities to refer to the newly-loaded entity.

También puede obtener una consulta LINQ que represente el contenido de una propiedad de navegación.You can also get a LINQ query that represents the contents of a navigation property.

Esto permite, entre otras acciones, ejecutar un operador de agregado en las entidades relacionadas sin cargarlas en la memoria.This allows you to do things such as running an aggregate operator over the related entities without loading them into memory.

using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);

    var postCount = context.Entry(blog)
        .Collection(b => b.Posts)
        .Query()
        .Count();
}

También puede filtrar las entidades relacionadas que se cargan en la memoria.You can also filter which related entities are loaded into memory.

using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);

    var goodPosts = context.Entry(blog)
        .Collection(b => b.Posts)
        .Query()
        .Where(p => p.Rating > 3)
        .ToList();
}

Carga diferidaLazy loading

La manera más simple de usar la carga diferida es instalar el paquete Microsoft.EntityFrameworkCore.Proxies y habilitarlo con una llamada a UseLazyLoadingProxies.The simplest way to use lazy-loading is by installing the Microsoft.EntityFrameworkCore.Proxies package and enabling it with a call to UseLazyLoadingProxies. Por ejemplo:For example:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseLazyLoadingProxies()
        .UseSqlServer(myConnectionString);

O al usar AddDbContext:Or when using AddDbContext:

.AddDbContext<BloggingContext>(
    b => b.UseLazyLoadingProxies()
          .UseSqlServer(myConnectionString));

EF Core habilitará la carga diferida de cualquier propiedad de navegación que se pueda invalidar, es decir, debe ser virtual y debe estar en una clase desde la que se pueda heredar.EF Core will then enable lazy loading for any navigation property that can be overridden--that is, it must be virtual and on a class that can be inherited from. Por ejemplo, en las entidades siguientes, las propiedades de navegación Post.Blog y Blog.Posts serán de carga diferida.For example, in the following entities, the Post.Blog and Blog.Posts navigation properties will be lazy-loaded.

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

    public virtual ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public virtual Blog Blog { get; set; }
}

Carga diferida sin servidores proxyLazy loading without proxies

Los servidores proxy de carga diferida funcionan inyectando el servicio ILazyLoader en una entidad, tal como se describe en Entity Type Constructors (Constructores de tipo de entidad).Lazy-loading proxies work by injecting the ILazyLoader service into an entity, as described in Entity Type Constructors. Por ejemplo:For example:

public class Blog
{
    private ICollection<Post> _posts;

    public Blog()
    {
    }

    private Blog(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private ILazyLoader LazyLoader { get; set; }

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

    public ICollection<Post> Posts
    {
        get => LazyLoader.Load(this, ref _posts);
        set => _posts = value;
    }
}

public class Post
{
    private Blog _blog;

    public Post()
    {
    }

    private Post(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private ILazyLoader LazyLoader { get; set; }

    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog
    {
        get => LazyLoader.Load(this, ref _blog);
        set => _blog = value;
    }
}

Esto no requiere tipos de entidad de los cuales heredar ni propiedades de navegación para ser virtual y permite que las instancias de entidad creadas con new se carguen de manera diferida una vez que se asocian a un contexto.This doesn't require entity types to be inherited from or navigation properties to be virtual, and allows entity instances created with new to lazy-load once attached to a context. Sin embargo, requiere una referencia al servicio ILazyLoader, que está definido en el paquete Microsoft.EntityFrameworkCore.Abstractions.However, it requires a reference to the ILazyLoader service, which is defined in the Microsoft.EntityFrameworkCore.Abstractions package. Este paquete contiene un conjunto mínimo de tipos, por lo que es muy poco el impacto al depender de él.This package contains a minimal set of types so that there is very little impact in depending on it. Sin embargo, para evitar por completo depender de cualquier paquete de EF Core en los tipos de entidad, es posible insertar el método ILazyLoader.Load como delegado.However, to completely avoid depending on any EF Core packages in the entity types, it is possible to inject the ILazyLoader.Load method as a delegate. Por ejemplo:For example:

public class Blog
{
    private ICollection<Post> _posts;

    public Blog()
    {
    }

    private Blog(Action<object, string> lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private Action<object, string> LazyLoader { get; set; }

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

    public ICollection<Post> Posts
    {
        get => LazyLoader.Load(this, ref _posts);
        set => _posts = value;
    }
}

public class Post
{
    private Blog _blog;

    public Post()
    {
    }

    private Post(Action<object, string> lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private Action<object, string> LazyLoader { get; set; }

    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog
    {
        get => LazyLoader.Load(this, ref _blog);
        set => _blog = value;
    }
}

El código anterior usa un método de extensión Load para que el uso del delegado sea un poco más limpio:The code above uses a Load extension method to make using the delegate a bit cleaner:

public static class PocoLoadingExtensions
{
    public static TRelated Load<TRelated>(
        this Action<object, string> loader,
        object entity,
        ref TRelated navigationField,
        [CallerMemberName] string navigationName = null)
        where TRelated : class
    {
        loader?.Invoke(entity, navigationName);

        return navigationField;
    }
}

Nota

El parámetro de constructor del delegado de carga diferida se debe denominar "lazyLoader".The constructor parameter for the lazy-loading delegate must be called "lazyLoader". La configuración para usar otro nombre está planificada para una versión futura.Configuration to use a different name than this is planned for a future release.

Como EF Core corregirá automáticamente las propiedades de navegación, puede terminar con ciclos en el grafo de objetos.Because EF Core will automatically fix-up navigation properties, you can end up with cycles in your object graph. Por ejemplo, cargar un blog y sus entradas relacionadas dará lugar a un objeto de blog que hará referencia a una colección de entradas.For example, loading a blog and its related posts will result in a blog object that references a collection of posts. Cada una de esas entradas tendrá una referencia de vuelta al blog.Each of those posts will have a reference back to the blog.

Algunos marcos de serialización no permiten ese tipo de ciclos.Some serialization frameworks do not allow such cycles. Por ejemplo, JSON.NET generará la excepción siguiente si se encuentra un ciclo.For example, Json.NET will throw the following exception if a cycle is encountered.

Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property 'Blog' with type 'MyApplication.Models.Blog'. (Newtonsoft.Json.JsonSerializationException: se detectó un bucle con autorreferencia para la propiedad "Blog" con el tipo "MyApplication.Models.Blog").Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property 'Blog' with type 'MyApplication.Models.Blog'.

Si usa ASP.NET Core, puede configurar JSON.NET para que omita los ciclos que encuentre en el grafo del objeto.If you are using ASP.NET Core, you can configure Json.NET to ignore cycles that it finds in the object graph. Esto se hace en el método ConfigureServices(...) en Startup.cs.This is done in the ConfigureServices(...) method in Startup.cs.

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddMvc()
        .AddJsonOptions(
            options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
        );

    ...
}

Otra alternativa consiste en decorar una de las propiedades de navegación con el atributo [JsonIgnore], que indica a JSON.NET que no recorra esa propiedad de navegación mientras se serializa.Another alternative is to decorate one of the navigation properties with the [JsonIgnore] attribute, which instructs Json.NET to not traverse that navigation property while serializing.