Consultas com acompanhamento versus Consultas sem acompanhamento

O comportamento de acompanhamento controla se o Entity Framework Core manterá as informações sobre uma instância de entidade em seu rastreador de alterações. Se uma entidade é acompanhada, qualquer alteração detectada na entidade será mantida no banco de dados durante SaveChanges. O EF Core também corrige as propriedades de navegação entre as entidades em um resultado de consulta com acompanhamento e as entidades que estão no rastreador de alterações.

Observação

Tipos de entidade sem chave nunca são acompanhados. Sempre que este artigo menciona tipos de entidade, ele se refere a tipos de entidade que têm uma chave definida.

Dica

Veja o exemplo deste artigo no GitHub.

Consultas com acompanhamento

Por padrão, as consultas que retornam tipos de entidade são de acompanhamento. Uma consulta com acompanhamento significa que todas as alterações nas instâncias de entidade são mantidas por SaveChanges. No exemplo a seguir, a alteração na classificação de blogs é detectada e mantida no banco de dados durante SaveChanges:

var blog = context.Blogs.SingleOrDefault(b => b.BlogId == 1);
blog.Rating = 5;
context.SaveChanges();

Quando os resultados são retornados em uma consulta com acompanhamento, o EF Core verifica se a entidade já está no contexto. Se o EF Core encontrar uma entidade existente, a mesma instância será retornada, o que pode potencialmente usar menos memória e ser mais rápido do que uma consulta sem acompanhamento. O EF Core não substitui os valores atuais e originais das propriedades da entidade na entrada pelos valores do banco de dados. Se a entidade não for encontrada no contexto, o EF Core criará uma nova instância de entidade e a anexará ao contexto. Os resultados da consulta não contém nenhuma entidade que é adicionada ao contexto, mas ainda não foi salva no banco de dados.

Consultas sem controle

As consultas sem acompanhamento são úteis quando os resultados são usados em um cenário de somente leitura. Geralmente, eles são mais rápidos para executar porque não há necessidade de configurar as informações de controle de alterações. Se as entidades recuperadas do banco de dados não precisarem ser atualizadas, uma consulta sem acompanhamento deverá ser usada. Uma consulta individual pode ser definida como sem acompanhamento. Uma consulta sem acompanhamento também fornece resultados com base no que está no banco de dados, desconsiderando quaisquer alterações locais ou entidades adicionadas.

var blogs = context.Blogs
    .AsNoTracking()
    .ToList();

O comportamento de acompanhamento padrão pode ser alterado no nível de instância do contexto:

context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

var blogs = context.Blogs.ToList();

A próxima seção explica quando uma consulta sem acompanhamento pode ser menos eficiente do que uma consulta com acompanhamento.

Resolução de identidade

Como uma consulta com acompanhamento usa o rastreador de alterações, o EF Core realiza a resolução de identidade em uma consulta com acompanhamento. Ao materializar uma entidade, o EF Core retornará a mesma instância de entidade do rastreador de alterações se ela já estiver sendo acompanhada. Se o resultado contiver a mesma entidade várias vezes, a mesma instância será retornada para cada ocorrência. Consultas sem acompanhamento:

  • Não use o rastreador de alterações e não realize a resolução de identidade.
  • Retorne uma nova instância da entidade mesmo quando a mesma entidade estiver contida no resultado várias vezes.

As consultas com e sem acompanhamento podem ser combinadas na mesma consulta. Ou seja, você pode ter uma consulta sem acompanhamento que realiza a resolução de identidade nos resultados. Assim como o operador consultável AsNoTracking, adicionamos outro operador AsNoTrackingWithIdentityResolution<TEntity>(IQueryable<TEntity>). Também há uma entrada associada adicionada na enumeração QueryTrackingBehavior. Quando a consulta para usar a resolução de identidade é configurada sem acompanhamento, um rastreador de alterações autônomo é usado em segundo plano ao gerar resultados de consulta para que cada instância seja materializada apenas uma vez. Como esse rastreador de alterações é diferente do que está no contexto, os resultados não são acompanhados pelo contexto. Depois que a consulta é totalmente enumerada, o rastreador de alterações sai do escopo e faz a coleta de lixo conforme necessário.

var blogs = context.Blogs
    .AsNoTrackingWithIdentityResolution()
    .ToList();

Configurar o comportamento de acompanhamento padrão

Se você estiver alterando o comportamento de acompanhamento para muitas consultas, talvez seja melhor alterar o padrão:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying.Tracking;Trusted_Connection=True")
        .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}

Isso torna todas as suas consultas sem acompanhamento por padrão. Você ainda pode adicionar AsTracking para transformar consultas específicas em consultas com acompanhamento.

Acompanhamento e projeções personalizadas

Mesmo se o tipo de resultado da consulta não for uma tipo de entidade, o EF Core ainda acompanhará os tipos de entidade contidos no resultado por padrão. Na consulta a seguir, que retorna um tipo anônimo, as instâncias do Blog no conjunto de resultados será rastreado.

var blog = context.Blogs
    .Select(
        b =>
            new { Blog = b, PostCount = b.Posts.Count() });

Se o conjunto de resultados contiver tipos de entidade provenientes da composição LINQ, o EF Core os acompanhará.

var blog = context.Blogs
    .Select(
        b =>
            new { Blog = b, Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault() });

Se o conjunto de resultados não contiver tipos de entidade, nenhum acompanhamento será realizado. Na consulta a seguir, retornamos um tipo anônimo com alguns dos valores da entidade (mas nenhuma instância do tipo de entidade real). Não há entidades acompanhadas saindo da consulta.

var blog = context.Blogs
    .Select(
        b =>
            new { Id = b.BlogId, b.Url });

O EF Core dá suporte à avaliação do cliente na projeção de nível superior. Se o EF Core materializar uma instância de entidade para avaliação do cliente, ela será acompanhada. Aqui, como estamos passando entidades blog para o método de cliente StandardizeURL, o EF Core também acompanhará as instâncias do blog.

var blogs = context.Blogs
    .OrderByDescending(blog => blog.Rating)
    .Select(
        blog => new { Id = blog.BlogId, Url = StandardizeUrl(blog) })
    .ToList();
public static string StandardizeUrl(Blog blog)
{
    var url = blog.Url.ToLower();

    if (!url.StartsWith("http://"))
    {
        url = string.Concat("http://", url);
    }

    return url;
}

O EF Core não acompanha as instâncias de entidade sem chave contidas no resultado. Mas o EF Core acompanha todas as outras instâncias de tipos de entidade com uma chave de acordo com as regras acima.

Versões anteriores

Antes da versão 3.0, o EF Core realizava o acompanhamento de forma diferente. As diferenças notáveis são as seguintes:

  • Conforme explicado na página Avaliação do cliente vs. do servidor, o EF Core dava suporte à avaliação do cliente em qualquer parte da consulta antes da versão 3.0. A avaliação do cliente causou a materialização de entidades, o que não fazia parte do resultado. Portanto, o EF Core analisou o resultado para detectar o que acompanhar. Esse design tinha as seguintes diferenças:

    • A avaliação do cliente na projeção, que causou a materialização, mas não retornou a instância de entidade materializada, não foi acompanhada. O exemplo a seguir não acompanhou entidades blog.

      var blogs = context.Blogs
          .OrderByDescending(blog => blog.Rating)
          .Select(
              blog => new { Id = blog.BlogId, Url = StandardizeUrl(blog) })
          .ToList();
      
    • Em determinados casos, o EF Core não acompanhou os objetos provenientes da composição LINQ. O exemplo a seguir não acompanhou Post.

      var blog = context.Blogs
          .Select(
              b =>
                  new { Blog = b, Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault() });
      
  • Sempre que os resultados da consulta continham tipos de entidade sem chave, a consulta inteira se transformava em sem acompanhamento. Isso significa que os tipos de entidade com chaves, que estão no resultado, também não estavam sendo acompanhados.

  • O EF Core realizava resolução de identidade em consultas sem acompanhamento. Ele usava referências fracas para controlar as entidades que já haviam sido retornadas. Portanto, se um conjunto de resultados contivesse a mesma entidade várias vezes, a mesma instância era obtida para cada ocorrência. No entanto, se um resultado anterior com a mesma identidade saísse do escopo e fizesse a coleta de lixo, o EF Core retornava uma nova instância.