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

Nota

Las versiones actuales de Visual Studio ofrecen opciones para completar código incorrecto y pueden provocar que las expresiones correctas se marquen como errores de sintaxis cuando se use el método ThenInclude después de una propiedad de navegación de colección.Current versions of Visual Studio offer incorrect code completion options and can cause correct expressions to be flagged with syntax errors when using the ThenInclude method after a collection navigation property. Este es un síntoma de un error de IntelliSense al que se hace seguimiento en https://github.com/dotnet/roslyn/issues/8237.This is a symptom of an IntelliSense bug tracked at https://github.com/dotnet/roslyn/issues/8237. Resulta seguro ignorar estos errores de sintaxis falsos siempre que el código sea correcto y se pueda compilar de manera adecuada.It is safe to ignore these spurious syntax errors as long as the code is correct and can be compiled successfully.

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

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

Inclusiones omitidasIgnored includes

Si modifica la consulta de manera que ya no devuelva instancias del tipo de entidad con que empezó la consulta, se omiten los operadores de inclusión.If you change the query so that it no longer returns instances of the entity type that the query began with, then the include operators are ignored.

En el ejemplo siguiente, los operadores de inclusión se basan en Blog, pero luego el operador Select se usa para cambiar la consulta para que devuelva un tipo anónimo.In the following example, the include operators are based on the Blog, but then the Select operator is used to change the query to return an anonymous type. En este caso, los operadores de inclusión no tienen ningún efecto.In this case, the include operators have no effect.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .Select(blog => new
        {
            Id = blog.BlogId,
            Url = blog.Url
        })
        .ToList();
}

De manera predeterminada, EF Core registrará una advertencia cuando se omitan los operadores de inclusión.By default, EF Core will log a warning when include operators are ignored. Consulte Logging (Registro) para más información sobre cómo ver la salida de registro.See Logging for more information on viewing logging output. Puede cambiar el comportamiento cuando se omite un operador de inclusión, ya sea para que se genere alguna excepción o para que no haga nada.You can change the behavior when an include operator is ignored to either throw or do nothing. Esto se hace al configurar las opciones del contexto, por lo general en DbContext.OnConfiguring o en Startup.cs si usa ASP.NET Core.This is done when setting up the options for your context - typically in DbContext.OnConfiguring, or in Startup.cs if you are using ASP.NET Core.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;ConnectRetryCount=0")
        .ConfigureWarnings(warnings => warnings.Throw(CoreEventId.IncludeIgnoredWarning));
}

Carga explícitaExplicit loading

Nota

Esta característica se introdujo en EF Core 1.1.This feature was introduced in EF Core 1.1.

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

Nota

Esta característica se introdujo en EF Core 2.1.This feature was introduced in EF Core 2.1.

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.