Como Resolver o Erro de Entity Tracking no Entity Framework Core

Se vocĂȘ trabalha com Entity Framework Core, existe um erro que costuma aparecer justamente quando parece que estamos fazendo algo simples: atualizar uma entidade.

The instance of entity type 'Produto' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked.

A primeira reação costuma ser olhar para o SaveChanges() e tentar descobrir o que estå acontecendo ali.

Mas, na maioria das vezes, o problema começou antes.

O Entity Framework Core jĂĄ estĂĄ acompanhando uma instĂąncia daquela entidade e, em algum momento do fluxo, outra instĂąncia com a mesma chave chega ao mesmo DbContext.

Foi exatamente esse tipo de situação que encontrei durante o desenvolvimento de uma API de rastreamento logístico.

Onde encontrei esse problema

Na época, eu estava implementando uma API para um sistema de rastreamento de logística.

O fluxo era, de forma simplificada, o seguinte: o CRM fazia uma requisição POST para a API, e a API recebia os acontecimentos relacionados à jornada de um chip até o cliente final.

Não era apenas uma informação de "entregue" ou "não entregue".

A API precisava receber e processar diferentes etapas da jornada, como:

Chip enviado
    ?
Chip entregue Ă  transportadora
    ?
CaminhĂŁo em trĂąnsito
    ?
Objeto em rota de entrega
    ?
Cliente nĂŁo encontrado na residĂȘncia
    ?
Nova tentativa de entrega
    ?
Entrega realizada

Cada acontecimento precisava ser registrado para que o sistema pudesse acompanhar a situação daquele produto.

Foi justamente nesse fluxo que apareceu o problema.

A entidade Produto estava envolvida no processamento e, quando determinada etapa era recebida, também era necessårio atualizar o histórico de auditoria para registrar o status correspondente àquela fase da entrega.

Em determinado momento desse processo, o Entity Framework Core jĂĄ estava acompanhando uma instĂąncia de Produto, enquanto outra instĂąncia representando o mesmo registro acabava sendo utilizada dentro daquele mesmo DbContext.

Foi aĂ­ que apareceu o erro de Entity Tracking.

Na época, a solução foi conduzida utilizando uma API que jå existia no projeto e que permitiu implementar a regra de forma diferente. A correção funcionou.

Como o código original desse projeto não estå mais disponível, não vou fingir que lembro exatamente cada linha da implementação ou apresentar aquele código como se fosse o código original.

O objetivo deste artigo é outro: explicar o problema que reconheci naquela situação e mostrar como entender e resolver esse tipo de conflito no Entity Framework Core.

O que Ă© o Entity Tracking?

O Entity Framework Core possui um mecanismo chamado Change Tracker.

Ele acompanha as entidades que estão sendo utilizadas pelo DbContext para saber o que mudou e, posteriormente, transformar essas alteraçÔes em operaçÔes no banco de dados.

Por exemplo:

var produto = await context.Produtos
    .FirstOrDefaultAsync(x => x.Id == 10);

Em uma consulta normal com tracking, o EF Core passa a acompanhar a instĂąncia retornada.

Podemos imaginar:

DbContext
   Š
   +-- Produto
        +-- Id = 10

Se alterarmos essa mesma instĂąncia:

produto.Nome = "Novo nome";
produto.Preco = 99.90m;

await context.SaveChangesAsync();

o EF Core consegue identificar as alteraçÔes e gerar o UPDATE correspondente.

Até aqui, tudo bem.

O problema aparece quando tentamos fazer o mesmo DbContext acompanhar outra instĂąncia que representa a mesma entidade e possui a mesma chave primĂĄria.

O que significa "outra instĂąncia"?

Imagine que o contexto jĂĄ carregou:

var produto = await context.Produtos
    .FirstOrDefaultAsync(x => x.Id == 10);

Agora criamos outro objeto:

var outroProduto = new Produto
{
    Id = 10,
    Nome = "Produto atualizado",
    Preco = 99.90m
};

Para o C#, sĂŁo dois objetos diferentes.

produto
Id = 10

outroProduto
Id = 10

Mas, para o Entity Framework Core, ambos representam a mesma entidade do banco.

O DbContext nĂŁo pode simplesmente decidir qual dos dois objetos deve representar o registro Id = 10.

Qual nome deveria ser utilizado?

Qual preço?

Qual relacionamento?

Qual estado?

Por isso existe o conceito de Identity Resolution.

Um determinado DbContext sĂł pode rastrear uma instĂąncia de uma entidade para uma determinada chave primĂĄria.

Como o erro acontece na prĂĄtica

Um exemplo simples seria:

var produto = await context.Produtos
    .FirstOrDefaultAsync(x => x.Id == 10);

var outroProduto = new Produto
{
Id = 10,
Nome = "Produto atualizado"
};

context.Update(outroProduto);

await context.SaveChangesAsync();

Nesse cenĂĄrio, o contexto jĂĄ estĂĄ acompanhando produto.

Depois tentamos colocar outroProduto, que também possui Id = 10, no mesmo contexto.

É justamente nesse momento que o Entity Framework Core pode lançar a exceção:

The instance of entity type 'Produto' cannot be tracked because another instance with the same key value is already being tracked.

O problema, portanto, nĂŁo Ă© simplesmente existirem dois objetos C# com o mesmo Id.

O problema Ă© tentar fazer o mesmo DbContext acompanhar duas instĂąncias que representam a mesma entidade.

Por que o Entity Framework Core nĂŁo permite isso?

Pode parecer uma limitação desnecessåria, mas existe uma razão.

Imagine que o contexto estivesse acompanhando:

Produto A
Id = 10
Nome = "Produto A"
Preço = 50

e também:

Produto B
Id = 10
Nome = "Produto B"
Preço = 100

Qual desses valores deveria ser enviado para o banco?

O EF Core teria que decidir qual objeto representa a versĂŁo correta da entidade.

A mesma ambiguidade pode acontecer com relacionamentos entre entidades.

Por isso o EF Core mantĂ©m uma Ășnica instĂąncia rastreada para determinada chave dentro do contexto.

Uma situação muito comum em APIs

Esse problema aparece bastante quando trabalhamos com APIs porque normalmente recebemos dados de uma requisição e precisamos transformå-los em entidades.

Imagine uma requisição como:

{
    "id": 10,
    "nome": "Produto atualizado",
    "preco": 99.90
}

O objeto recebido pela API pode ser diferente da entidade que jĂĄ foi carregada pelo DbContext.

Temos entĂŁo:

Requisição HTTP
      ?
Objeto recebido
      ?
Produto Id = 10

```
         +
```

Banco de dados
?
Entidade carregada
?
Produto Id = 10

Se essas duas instĂąncias forem colocadas para serem rastreadas pelo mesmo contexto, temos o cenĂĄrio clĂĄssico do erro.

Foi justamente esse tipo de situação que tornou o problema que encontrei no projeto de rastreamento logístico interessante para mim.

O erro nĂŁo estava simplesmente em "atualizar um produto".

Ele estava relacionado ao estado das entidades dentro daquele fluxo de processamento.

Solução 1: trabalhar com a entidade que jå estå sendo rastreada

Quando temos uma entidade existente no banco, uma das abordagens mais simples Ă© carregar a entidade e modificar a prĂłpria instĂąncia retornada pelo contexto.

Por exemplo:

var produto = await context.Produtos
    .FirstOrDefaultAsync(x => x.Id == id);

if (produto == null)
{
return;
}

produto.Nome = "Novo nome";
produto.Preco = 99.90m;

await context.SaveChangesAsync();

Aqui existe apenas uma instĂąncia de Produto sendo acompanhada pelo contexto.

O fluxo fica:

Banco
 ?
Produto
 ?
DbContext acompanha a entidade
 ?
Alteramos os valores
 ?
SaveChanges()
 ?
UPDATE

Para muitos cenårios de atualização, essa é uma abordagem simples e previsível.

Solução 2: copiar os valores para a entidade rastreada

Existem situaçÔes em que temos um objeto recebido pela API e uma entidade correspondente jå carregada pelo contexto.

Nesse caso, podemos copiar os valores do objeto recebido para a entidade que jĂĄ estĂĄ sendo rastreada.

Por exemplo:

var produto = await context.Produtos
    .FirstOrDefaultAsync(x => x.Id == produtoAtualizado.Id);

if (produto == null)
{
return;
}

context.Entry(produto)
.CurrentValues
.SetValues(produtoAtualizado);

await context.SaveChangesAsync();

A ideia Ă© importante:

produtoAtualizado
       Š
       Š SetValues()
       ?
produto jĂĄ rastreado
       Š
       ?
SaveChanges()

NĂŁo estamos tentando colocar uma segunda instĂąncia de Produto no contexto.

Estamos atualizando a instĂąncia que o contexto jĂĄ conhece.

O SetValues() Ă© especialmente Ăștil para copiar valores de propriedades escalares. Ele nĂŁo deve ser entendido como uma solução automĂĄtica para sincronizar todo um grafo de entidades e relacionamentos.

Se existirem entidades relacionadas, elas precisam ser tratadas de acordo com o cenĂĄrio.

Solução 3: atualizar somente a chave estrangeira quando for suficiente

Outro cenĂĄrio bastante comum envolve entidades relacionadas.

Imagine:

public class Produto
{
    public int Id { get; set; }
    public string Nome { get; set; }

```
public int CategoriaId { get; set; }
public Categoria Categoria { get; set; }
```

}

Se precisamos apenas mudar a categoria do produto, talvez nĂŁo seja necessĂĄrio criar uma nova instĂąncia de Categoria.

Podemos simplesmente alterar a chave estrangeira:

produto.CategoriaId = novaCategoriaId;

Isso Ă© diferente de fazer:

produto.Categoria = new Categoria
{
    Id = novaCategoriaId
};

A segunda abordagem introduz outra instĂąncia de Categoria no grafo de entidades.

Dependendo do que jå estiver sendo rastreado pelo contexto, isso pode criar mais uma situação de conflito.

Se tudo que precisamos informar Ă©:

"Este produto agora pertence Ă  categoria 5."

entĂŁo muitas vezes:

produto.CategoriaId = 5;

Ă© suficiente.

Solução 4: AsNoTracking() para consultas que não precisam ser alteradas

Outra ferramenta importante do EF Core Ă©:

AsNoTracking()

Por exemplo:

var produto = await context.Produtos
    .AsNoTracking()
    .FirstOrDefaultAsync(x => x.Id == id);

Nesse caso, o EF Core não mantém aquela entidade no Change Tracker da maneira que uma consulta normal com tracking faria.

Essa abordagem Ă© bastante Ăștil para consultas que serĂŁo utilizadas apenas para leitura.

Por exemplo:

var produtos = await context.Produtos
    .AsNoTracking()
    .ToListAsync();

Se a finalidade da consulta Ă© simplesmente retornar dados para uma tela ou resposta de API, nĂŁo existe necessariamente motivo para manter todas aquelas entidades sendo rastreadas.

Mas cuidado

AsNoTracking() não é uma solução genérica para qualquer erro de Entity Tracking.

Eu nĂŁo recomendo pensar:

"Deu erro de Entity Tracking? Vou colocar AsNoTracking()."

Isso pode simplesmente esconder o problema ou criar outro.

Se vocĂȘ precisa:

1. consultar uma entidade;
2. modificar essa entidade;
3. chamar SaveChanges();

o tracking pode ser exatamente o comportamento que vocĂȘ precisa.

Por isso, antes de utilizar essa opção, pergunte:

Essa consulta realmente precisa ser rastreada?

Se a resposta for nĂŁo, AsNoTracking() pode fazer sentido.

Solução 5: verificar o tempo de vida do DbContext

Essa é uma parte que merece bastante atenção.

O DbContext nĂŁo foi projetado para viver indefinidamente.

Ele representa normalmente uma unidade de trabalho: criar o contexto, consultar ou anexar entidades, fazer as alteraçÔes, executar SaveChanges() e então encerrar aquele contexto.

Um problema comum Ă© reutilizar a mesma instĂąncia por tempo demais.

Imagine:

Request 1
   ?
DbContext
   ?
Produto Id = 10
   ?
continua vivo...

Request 2
?
mesmo DbContext
?
Produto Id = 10

Quanto mais tempo o mesmo contexto permanece vivo, maior pode ficar a quantidade de entidades que ele estĂĄ acompanhando.

Em uma API, isso merece atenção especial.

Em aplicaçÔes ASP.NET Core utilizando injeção de dependĂȘncia, o padrĂŁo comum Ă© registrar o DbContext para uma unidade de trabalho associada Ă  requisição.

Portanto, antes de começar a colocar Detach, AsNoTracking() ou outras soluçÔes espalhadas pelo código, vale perguntar:

Meu DbContext estĂĄ sendo utilizado pelo tempo correto?

Se o contexto estiver sendo compartilhado entre diferentes operaçÔes que deveriam ser independentes, o problema pode estar na arquitetura do fluxo e não naquela entidade específica.

Como descobrir o que o DbContext estĂĄ rastreando?

Quando o erro nĂŁo Ă© Ăłbvio, uma das ferramentas mais Ășteis Ă© olhar o prĂłprio Change Tracker.

Podemos fazer:

var entidades = context.ChangeTracker
    .Entries()
    .ToList();

Durante uma investigação, isso permite verificar quais entidades estão sendo acompanhadas naquele momento.

Por exemplo, imagine encontrar algo semelhante a:

Produto    Id = 10    Modified
Categoria  Id = 5     Unchanged
Produto    Id = 10    Added

Agora temos uma pista muito mais concreta.

Existe mais de uma instĂąncia de Produto com a mesma chave tentando participar do tracking daquele contexto.

Essa investigação costuma ser muito mais Ăștil do que simplesmente tentar uma solução aleatĂłria.

E o Detach?

Também é possível remover uma entidade do tracking:

context.Entry(produtoExistente).State =
    EntityState.Detached;

Depois disso, outra instĂąncia pode ser anexada ou atualizada.

Por exemplo:

context.Entry(produtoExistente).State =
    EntityState.Detached;

context.Update(produtoAtualizado);

await context.SaveChangesAsync();

Isso pode ser Ăștil em situaçÔes especĂ­ficas.

Mas eu não trataria Detach() como a primeira solução para um erro de Entity Tracking.

O motivo Ă© que precisamos entender exatamente o que estĂĄ sendo removido do tracking e quais entidades fazem parte do grafo que serĂĄ anexado novamente.

Em outras palavras:

Detach() pode resolver o sintoma sem necessariamente resolver a causa.

Antes de utilizĂĄ-lo, vale investigar por que duas instĂąncias chegaram ao mesmo DbContext.

E o EnableSensitiveDataLogging()?

Durante uma investigação em ambiente de desenvolvimento, também podemos habilitar:

optionsBuilder
    .EnableSensitiveDataLogging();

Isso pode fornecer informaçÔes adicionais nos logs e ajudar a identificar os valores envolvidos no problema.

Mas Ă© importante ter cuidado.

Essa opção pode fazer informaçÔes potencialmente sensíveis aparecerem nos logs. Portanto, não é algo que eu recomendaria deixar habilitado indiscriminadamente em produção.

Use principalmente durante o diagnĂłstico e de maneira controlada.

Então, qual solução devo usar?

Não existe uma solução universal.

O primeiro passo deve ser descobrir por que duas instĂąncias da mesma entidade estĂŁo chegando ao mesmo DbContext.

Depois disso, a solução fica muito mais clara.

Situação Abordagem
Entidade jĂĄ estĂĄ sendo rastreada Modificar a prĂłpria entidade
API recebeu novos valores Copiar valores para a entidade rastreada
Precisa alterar apenas uma relação Alterar a chave estrangeira
Consulta somente para leitura Avaliar AsNoTracking()
Contexto sendo reutilizado por tempo demais Corrigir o ciclo de vida do DbContext
NĂŁo sabe o que estĂĄ sendo rastreado Inspecionar ChangeTracker
Precisa substituir uma instĂąncia por outra Avaliar Detach com cuidado

O mais importante é não escolher uma dessas alternativas simplesmente porque ela fez a exceção desaparecer.

Primeiro entenda o fluxo das entidades. Depois escolha a abordagem adequada.

O que eu aprendi com esse problema

O interessante desse tipo de erro Ă© que ele pode parecer completamente desconectado da causa real.

VocĂȘ estĂĄ processando um evento.

Precisa atualizar uma informação.

Chama o SaveChanges().

E de repente recebe uma exceção dizendo que uma entidade jå estå sendo rastreada.

Foi exatamente esse tipo de situação que encontrei naquela API de rastreamento.

O sistema precisava processar os eventos da jornada do chip e atualizar o histĂłrico de auditoria correspondente Ă  etapa da entrega. O problema apareceu envolvendo a entidade Produto, dentro desse fluxo.

Na época, a solução que me apresentaram funcionou e o desenvolvimento continuou.

Hoje, olhando para aquele problema com mais conhecimento sobre o funcionamento do Entity Framework Core, fica mais fåcil entender o que estava acontecendo por trås da exceção.

O ponto principal Ă© que o Entity Framework nĂŁo estĂĄ simplesmente reclamando porque existem dois objetos iguais.

Ele estå tentando evitar uma situação em que o mesmo DbContext tenha duas representaçÔes diferentes da mesma entidade.

Por isso, quando esse erro aparecer, minha primeira pergunta seria:

Qual instĂąncia dessa entidade o meu DbContext jĂĄ estĂĄ rastreando?

Depois:

Por que meu cĂłdigo estĂĄ tentando trabalhar com outra instĂąncia da mesma entidade?

Essas duas perguntas normalmente levam muito mais råpido à causa do problema do que simplesmente sair adicionando AsNoTracking(), Detach() ou Update() até a exceção desaparecer.

E essa, para mim, Ă© a principal lição do Entity Tracking: nĂŁo tente apenas fazer o erro desaparecer. Entenda o que o DbContext estĂĄ rastreando e por quĂȘ.

Este conteĂșdo foi Ăștil para vocĂȘ?

ComentĂĄrios (1)

Show! 25/08/2026 20:44

Já vou comprar.