Carga diferida de datos relacionados

Carga diferida con servidores proxy

La manera más simple de usar la carga diferida es instalar el paquete Microsoft.EntityFrameworkCore.Proxies y habilitarlo con una llamada a UseLazyLoadingProxies. Por ejemplo:

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

O al usar 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. Por ejemplo, en las entidades siguientes, las propiedades de navegación Post.Blog y Blog.Posts serán de carga diferida.

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

Advertencia

La carga diferida puede provocar recorridos de ida y vuelta a la base de datos adicionales e innecesarios (el problema conocido como N+1), y conviene tener cuidado para evitar esto. Consulte la sección sobre el rendimiento para obtener más información.

Carga diferida sin servidores proxy

La carga diferida sin servidores proxy funciona inyectando el servicio ILazyLoader en una entidad, tal como se describe en Tipos de entidad con constructores. Por ejemplo:

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

Este método 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. Sin embargo, requiere una referencia al servicio ILazyLoader, que está definido en el paquete Microsoft.EntityFrameworkCore.Abstractions. Este paquete contiene un conjunto mínimo de tipos, por lo que el impacto al depender de él es poco significativo. Sin embargo, para evitar depender por completo de ningún paquete de EF Core en los tipos de entidad, es posible insertar el método ILazyLoader.Load como delegado. Por ejemplo:

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:

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". La configuración para usar otro nombre está planificada para una versión futura.