Como carregar dados relacionadosLoading Related Data

O Entity Framework Core permite que você use as propriedades de navegação em seu modelo para carregar as entidades relacionadas.Entity Framework Core allows you to use the navigation properties in your model to load related entities. Há três padrões de O/RM comuns usados para carregar os dados relacionados.There are three common O/RM patterns used to load related data.

  • Carregamento adiantado significa que os dados relacionados são carregados do banco de dados como parte da consulta inicial.Eager loading means that the related data is loaded from the database as part of the initial query.
  • Carregamento explícito significa que os dados relacionados são explicitamente carregados do banco de dados em um momento posterior.Explicit loading means that the related data is explicitly loaded from the database at a later time.
  • Carregamento lento significa que os dados relacionados são carregados de modo transparente do banco de dados quando a propriedade de navegação é acessada.Lazy loading means that the related data is transparently loaded from the database when the navigation property is accessed.

Dica

Veja o exemplo deste artigo no GitHub.You can view this article's sample on GitHub.

Carregamento adiantadoEager loading

Você pode usar o método Include para especificar os dados relacionados a serem incluídos nos resultados da consulta.You can use the Include method to specify related data to be included in query results. No exemplo a seguir, os blogs que são retornados nos resultados terão suas propriedades Posts preenchidas com as postagens relacionadas.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();
}

Dica

O Entity Framework Core corrigirá automaticamente as propriedades de navegação para outras entidades que forem carregadas anteriormente na instância do contexto.Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. Então, mesmo se você não incluir de forma explícita os dados para a propriedade de navegação, a propriedade ainda pode ser populada se algumas ou todas as entidades relacionadas foram carregadas anteriormente.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.

Você pode incluir dados relacionados de várias relações em uma única 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();
}

Incluindo vários níveisIncluding multiple levels

Você pode fazer uma busca detalhada por meio de relações para incluir vários níveis de dados relacionados usando o método ThenInclude.You can drill down through relationships to include multiple levels of related data using the ThenInclude method. O exemplo a seguir carrega todos os blogs, suas postagens relacionadas e o autor de cada postagem.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();
}

É possível encadear chamadas múltiplas a ThenInclude para continuar incluindo outros níveis de dados 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();
}

Você pode combinar tudo isso para incluir dados relacionados de vários níveis e várias raízes na mesma 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();
}

Você talvez queira incluir várias entidades relacionadas para uma das entidades que está sendo incluída.You may want to include multiple related entities for one of the entities that is being included. Por exemplo, ao consultar os Blogs, você inclui os Posts e, em seguida, o Author e as Tags dos Posts.For example, when querying Blogs, you include Posts and then want to include both the Author and Tags of the Posts. Para fazer isso, você precisará especificar cada uma para incluir o caminho a partir da raiz.To do this, you need to specify each include path starting at the root. Por exemplo, Blog -> Posts -> Author e Blog -> Posts -> Tags.For example, Blog -> Posts -> Author and Blog -> Posts -> Tags. Isso não significa que você obterá junções redundantes. Na maioria dos casos, o EF consolidará as junções ao gerar o 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();
}

Cuidado

Desde a versão 3.0.0, cada Include fará com que uma junção adicional seja adicionada às consultas SQL produzidas por provedores relacionais, enquanto as versões anteriores geraram consultas SQL adicionais.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. Isso pode alterar significativamente o desempenho de suas consultas, para melhor ou pior.This can significantly change the performance of your queries, for better or worse. Em particular, as consultas LINQ com um número extremamente alto de operadores de Include podem precisar ser divididas em várias consultas LINQ separadas para evitar o problema de explosão cartesiano.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.

Incluir para tipos derivadosInclude on derived types

Você pode incluir dados relacionados de navegações definidas apenas em um tipo derivado usando Include e ThenInclude.You can include related data from navigations defined only on a derived type using Include and ThenInclude.

Com o seguinte modelo: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; }
}

Conteúdo da navegação em School de todas as pessoas que são alunos pode ser carregada de maneira adiantada usando um número de padrões:Contents of School navigation of all People who are Students can be eagerly loaded using a number of patterns:

  • usando conversãousing cast

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

    context.People.Include(person => (person as Student).School).ToList()
    
  • usando a sobrecarga de Include que usa o parâmetro de tipo stringusing overload of Include that takes parameter of type string

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

Carregamento explícitoExplicit loading

Você pode carregar explicitamente uma propriedade de navegação pela 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();
}

Você também pode carregar explicitamente uma propriedade de navegação executando uma consulta separada que retorna as entidades relacionadas.You can also explicitly load a navigation property by executing a separate query that returns the related entities. Se o controle de alterações estiver habilitado, ao carregar uma entidade, o EF Core automaticamente definirá as propriedades de navegação da entidade recém-carregada para se referirem a alguma entidade já carregada e definir as propriedades de navegação das entidades já carregadas para se referirem à entidade recém-carregada.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.

Você também pode obter uma consulta LINQ que representa o conteúdo de uma propriedade de navegação.You can also get a LINQ query that represents the contents of a navigation property.

Isso permite que você faça coisas como executar um operador de agregação sobre as entidades relacionadas sem carregá-las na memória.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();
}

Você também pode filtrar quais entidades relacionadas são carregadas na memória.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();
}

Carregamento lentoLazy loading

A maneira mais simples para usar o carregamento lento é instalando o pacote Microsoft.EntityFrameworkCore.Proxies e habilitá-lo com uma chamada para 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 exemplo:For example:

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

Ou ao usar AddDbContext:Or when using 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.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 exemplo, nas entidades a seguir, as propriedades de navegação Post.Blog e Blog.Posts serão de carregamento lento.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; }
}

Carregamento lento sem proxiesLazy loading without proxies

Os proxies de carregamento lento funcionam inserindo o serviço ILazyLoader em uma entidade, conforme descrito em Construtores de tipo de entidade.Lazy-loading proxies work by injecting the ILazyLoader service into an entity, as described in Entity Type Constructors. Por exemplo: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;
    }
}

Isso não exige que os tipos de entidade sejam herdados de ou as propriedades de navegação sejam virtuais e permitam que as instâncias da entidade criadas com new sejam carregados de maneira lenta assim que forem conectadas a um 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. No entanto, isso exige referência ao serviço ILazyLoader, que é definido no pacote Microsoft.EntityFrameworkCore.Abstractions.However, it requires a reference to the ILazyLoader service, which is defined in the Microsoft.EntityFrameworkCore.Abstractions package. Este pacote contém um conjunto mínimo de tipos, portanto, há pouco impacto em depender dele.This package contains a minimal set of types so that there is very little impact in depending on it. No entanto, para evitar completamente a dependência de todos os pacotes do EF Core em tipos de entidade, é possível injetar o método ILazyLoader.Load como um 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 exemplo: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;
    }
}

O código acima usa um método de extensão Load para deixar o representante um pouco mais limpo: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;
    }
}

Observação

O parâmetro de construtor para o representante de carregamento lento deve ser chamado de "lazyLoader".The constructor parameter for the lazy-loading delegate must be called "lazyLoader". A configuração para usar um nome diferente é planejada para uma versão futura.Configuration to use a different name than this is planned for a future release.

Como o EF Core automaticamente corrigirá as propriedades de navegação, você poderá acabar com ciclos em seu gráfico de objeto.Because EF Core will automatically fix-up navigation properties, you can end up with cycles in your object graph. Por exemplo, o carregamento de um blog e suas postagens relacionadas resultará em um objeto de blog que referencia uma coleção de postagens.For example, loading a blog and its related posts will result in a blog object that references a collection of posts. Cada uma dessas postagens terá uma referência de volta para o blog.Each of those posts will have a reference back to the blog.

Algumas estruturas de serialização não permitem esses ciclos.Some serialization frameworks do not allow such cycles. Por exemplo, Json.NET gerará a exceção a seguir se for encontrado um ciclo.For example, Json.NET will throw the following exception if a cycle is encountered.

Newtonsoft.Json.JsonSerializationException: loop autorreferenciado detectado para a propriedade 'Blog' com tipo 'MyApplication.Models.Blog'.Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property 'Blog' with type 'MyApplication.Models.Blog'.

Se você estiver usando o ASP.NET Core, poderá configurar o Json.NET para ignorar os ciclos que encontrar no gráfico de objeto.If you are using ASP.NET Core, you can configure Json.NET to ignore cycles that it finds in the object graph. Isso é feito no método ConfigureServices(...) no 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
        );

    ...
}

Outra alternativa é decorar uma das propriedades de navegação com o atributo [JsonIgnore], que instrui o Json.NET para não percorrer essa propriedade de navegação durante a serialização.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.