Na minha API Web do ASP.NET Core 9 e no Entity Framework Core, tenho uma classe de modelo para a Client
que tem um relacionamento muitos para muitos com a classe de modelo Channel
.
public class Client
{
[Key]
public long ID { get; set; }
}
public class Channel
{
[Key]
public long ID { get; set; }
public string? Name { get; set; }
public ICollection<Client>? Client { get; set; }
}
Após a migração, o banco de dados tem a estrutura de uma nova tabela como eu esperava:
Posso adicionar novos valores usando o Entity Framework Core.
O problema começa quando quero atualizar os valores.
Tenho uma API, com um PUT
verbo para ser preciso, que recebe o Client
objeto como parâmetro com todos os detalhes. Primeiro leio o objeto do banco de dados, incluindo o Channels
:
var localClient = await db.Client.AsNoTracking()
.Include(c => c.Channels)
.FirstOrDefaultAsync(model => model.ID == id);
Então, mapeio o parâmetro com os dados do banco de dados:
localClient = mapper.Map<Domain.Client>(client);
E então eu atualizo Channels
usando os valores do parâmetro:
localClient.Channels?.Clear();
if (client.Channels != null)
{
var listChannels = client.Channels.ToList();
foreach (Channel ch in listChannels)
{
var l = await db.Channels.Where(c => c.ID == ch.ID).FirstOrDefaultAsync();
if (l != null)
if (localClient.Channels!.Count(c => c.ID == l.ID) == 0)
localClient.Channels?.Add(l);
}
}
Se eu inspecionar o localClient
objeto, há apenas canais exclusivos e nenhuma duplicação. Quando eu quero salvar usando
db.Attach(client);
Recebo imediatamente este erro:
A instância do tipo de entidade 'Channel' não pode ser rastreada porque outra instância com o mesmo valor de chave para {'ID'} já está sendo rastreada. Ao anexar entidades existentes, garanta que apenas uma instância de entidade com um determinado valor de chave seja anexada. Considere usar 'DbContextOptionsBuilder.EnableSensitiveDataLogging' para ver os valores de chave conflitantes.
Não consigo entender por que recebo esse erro. Verifiquei meus projetos antigos e uso um processo semelhante.
Atualizar
client
é um objeto que passo via API: ele contém todos os detalhes sobre o cliente, também a lista de canais.
group.MapPut("/{id}",
async Task<Results<Ok, NotFound>> (long id, Domain.Client client,
MyDbContext db, IMapper mapper) =>
{
// code above
}
Busquei client
no banco de dados porque pensei que o erro estava relacionado a uma nova instância ou registro em vez de atualizar um existente.
Atualização/2
Criei no GitHub um pequeno projeto para testar a atualização. Apliquei as sugestões abaixo, mas o objeto não é atualizado no banco de dados.
Evite usar
Clear
e tentar vincular os itens de volta. Além disso, seu carregamento e mapeamento não estão fazendo o que você provavelmente pensa que eles estão fazendo. Este código:mapper.Map() substituirá a referência, tornando a chamada para buscar o cliente local completamente inútil. O que você quer em vez de /w Automapper é:
Isso diz ao Automapper para copiar valores do DTO (cliente passado) para o cliente local carregado. A configuração de mapeamento usada para copiar valores deve ignorar propriedades de navegação baseadas em coleção onde você deseja associar/desassociar referências. O Automapper não será capaz de lidar com isso automaticamente.
Evite também a muleta de
OrDefault()
a menos que esteja preparado para, e lide com a possibilidade de nada retornar. Ao consultar o banco de dados (Linq-to-DB) use.Single()
or.First()
está bom se consultar por PK. Ao consultar conjuntos na memória, incluindo o.Local
cache de rastreamento, use.First()
. Se um registro não for encontrado, obtemos uma exceção naquela linha em vez de emNullReferenceException
algum lugar mais tarde, onde pode haver várias referências que podem ser #null inesperadamente.Quando se trata de lidar com a associação e dissociação de canais, você deve fazer isso adicionando e subtraindo referências do conjunto conforme necessário. A abordagem
Clear()
andAddRange()
levará a problemas de referência.Referências são tudo com EF, então no seu código original você estava mapeando uma nova instância desanexada de Client, incluindo Channels usando o Mapper, e então limpando os canais. Isso não remove as referências de canal do cache de rastreamento, então adicionar novas cópias desanexadas leva a erros de rastreamento.