EF Core  -  Apresentando Identity Resolution - I


 Hoje vou apresentar o recurso Identity Resolution disponível a partir da  EF Core 5.0.

Vamos iniciar definindo o que é o Identity Resolution.

Quando qualquer entidade é atualizada, adicionada ou excluída, o contexto do EF Core rastreia as entidades. As entidades podem ser rastreadas pelo DbContext somente se essa entidade tiver uma chave primária e, isso significa que, se uma entidade for uma entidade sem chave, ela não poderá ser rastreada pelo DbContext.

Mas a questão é: como o DbContext rastreia essas entidades ?

Ele pode rastrear vários objetos que têm a mesma chave primária ?

Um DbContext pode rastrear apenas uma instância de uma determinada chave primária, e assim, várias instâncias de uma entidade com o mesmo valor de chave devem ser resolvidas para uma única instância.

Para entender este conceito vamos dar um exemplo bem simples:

Para este exemplo eu vou usar uma Web API criada na ASP .NET Core 6 chamada APICatalogo onde temos o controlador CategoriasController que contém os endpoints para realizar o CRUD usando uma instância do contexto do EF Core 6 definida no arquivo AppDbContext que foi injetado no construtor do controlador:

private readonly AppDbContext _context;

public CategoriasController(AppDbContext context)
{
     _context = context;
}

Vou destacar o método PUT que foi ajustado para o exemplo deste artigo:

[HttpPut("TesteID/{id:int}")]
public ActionResult TesteIDResolution(int id, Categoria categoria)
{

    var item = _context.Categorias.FirstOrDefault(p => p.CategoriaId == id);

    var novoCategoria = new Categoria()
    {
      CategoriaId =
item.CategoriaId, 
      Nome = "Nova Categoria"
    };

    try
    {
      _context.Entry(categoria).State = EntityState.Modified;
      _context.SaveChanges();
      return Ok(categoria);

    }
    catch (Exception ex)
    {
       throw new Exception($"{nameof(item)} não poder ser atualizada : {ex.Message}");
    }
}

Neste código estamos recebendo o Id da Categoria e os dados para realizar uma atualização.

- Primeiro localizamos e retornamos um objeto Categoria pelo seu Id.
- A seguir criamos um novo objeto Categoria atribuindo o mesmo Id.
- Na sequência temos um bloco try/catch onde vamos atualizar a Categoria.

Ao executar este projeto e tentar realizar a atualização para uma categoria existente vamos usar a interface do Swagger e informar o Id da Categoria e novos dados que desejamos alterar:

A clicar em Execute , iremos obter uma exceção conforme mostrada a seguir:

Vejamos a mensagem de erro traduzida:

"A instância do tipo de entidade 'Categoria' não pode ser rastreada porque outra instância com o mesmo valor de chave para {'CategoriaId'} já está sendo rastreada. Ao anexar entidades existentes, certifique-se de que apenas uma instância de entidade com um determinado valor de chave esteja anexada. Considere usar 'DbContextOptionsBuilder.EnableSensitiveDataLogging' para ver os valores de chave conflitantes."

Assim um DbContext só pode acompanhar uma instância de entidade com qualquer valor de chave primária fornecido. Isso significa que várias instâncias de uma entidade com o mesmo valor de chave devem ser resolvidas para uma única instância.

Isso é chamado de Identity Resolution ou "resolução de identidade" e garante que o EF Core esteja rastreando um gráfico consistente sem ambiguidades sobre os relacionamentos ou valores de propriedade das entidades.

O erro acima pode ocorrer nas seguintes situações:

Mas porque precisamos do Identity Resolution ?

O EF Core rastreia uma única instância para um determinado valor de chave primária por dois motivos :

  1. Os valores de propriedade podem ser diferentes entre várias instâncias. Ao atualizar o banco de dados, o EF Core precisa saber quais valores de propriedade usar.
     
  2. Várias instâncias podem ter dados diferentes para uma ou mais propriedades além da chave primária. Assim, seria difícil para o EF Core entender quais propriedades são alteradas e quais não são e quais propriedades devem ser usadas durante a atualização do banco de dados;

Dessa forma a resolução de identidade ocorre automaticamente quando as entidades são rastreadas em uma consulta. Isso significa que, se uma instância de entidade com um determinado valor de chave já estiver controlada, essa instância controlada existente será usada em vez de criar uma nova instância.

Isso tem uma consequência importante: se os dados tiverem sido alterados no banco de dados, isso não será refletido nos resultados da consulta. Esse é um bom motivo para usar uma nova instância de DbContext para cada unit-of-work.

Já as consultas sem rastreamento (AsNoTracking) não executam a resolução de identidade porque isso afeta o desempenho do streaming de um grande número de entidades de uma consulta, e, isso ocorre porque a resolução de identidade requer o acompanhamento de cada instância retornada para que possa ser usada em vez de criar uma duplicata posteriormente.(Como consequência consultas sem rastreamento podem retornar valores duplicados.)

Evitando problemas

Considere que o DbContext foi projetado para representar uma unidade de trabalho de curta duração. Não seguir essas diretrizes facilita a execução de situações em que é feita uma tentativa de rastrear várias instâncias da mesma entidade. Alguns exemplos comuns são:

Agindo assim você vai evitar dores de cabeça com seu código...

E estamos conversados...

Referências:


José Carlos Macoratti