Carregamento adiado de dados relacionados

Carregamento adiado com proxies

A maneira mais simples para usar o carregamento lento é instalando o pacote Microsoft.EntityFrameworkCore.Proxies e habilitá-lo com uma chamada para UseLazyLoadingProxies. Por exemplo:

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

Ou ao usar AddDbContext:

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

O EF Core, em seguida, habilitará o carregamento lento para qualquer propriedade de navegação que pode ser substituída – ou seja, deverá ser virtual e em uma classe que pode ser herdada. Por exemplo, nas entidades a seguir, as propriedades de navegação Post.Blog e Blog.Posts serão de carregamento lento.

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

Aviso

O carregamento adiado pode gerar idas e vindas adicionais desnecessárias do banco de dados (o que chamamos de problema N + 1) e é preciso tomar cuidado para evitar isso. Confira a seção de desempenho para obter mais detalhes.

Carregamento lento sem proxies

O carregamento adiado sem proxies funciona injetando o serviço ILazyLoader em uma entidade, conforme descrito em Construtores de tipo de entidade. Por exemplo:

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

Esse método não exige que os tipos de entidade sejam herdados nem que as propriedades de navegação sejam virtuais e permite que as instâncias da entidade criadas com new sejam carregados de maneira adiada assim que anexadas a um contexto. No entanto, isso exige referência ao serviço ILazyLoader, que é definido no pacote Microsoft.EntityFrameworkCore.Abstractions. Este pacote contém um conjunto mínimo de tipos, portanto, há pouco impacto em depender dele. No entanto, para evitar completamente a dependência de todos os pacotes do EF Core nos tipos de entidade, é possível injetar o método ILazyLoader.Load como um delegado. Por exemplo:

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

O código acima usa um método de extensão Load para deixar o representante um pouco mais limpo:

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

Observação

O parâmetro de construtor para o representante de carregamento lento deve ser chamado de "lazyLoader". A configuração para usar um nome diferente é planejada para uma versão futura.