A razão do erro no código fornecido é a seguir.
Quando você cria uma entidade A
do banco de dados, sua propriedade S
é inicializada com uma coleção que contém dois novos registros B
. Id
de cada uma dessas novas B
entidades é igual a 0
.
// This line of code reads entity from the database
// and creates new instance of object A from it.
var a = db.Set<A>().Single();
// When new entity A is created its field S initialized
// by a collection that contains two new instances of entity B.
// Property Id of each of these two B entities is equal to 0.
public ICollection<B> S { get; set; } = new List<B>() { new B {}, new B {} };
Após a execução da linha de var a = db.Set<A>().Single()
coleção de código S
da entidade, A
ela não contém B
entidades do banco de dados, porque DbContext Db
não usa carregamento lento e não há carregamento explícito da coleção S
. A entidade A
contém apenas novas B
entidades que foram criadas durante a inicialização da coleção S
.
Quando você chama IsModifed = true
a S
estrutura da entidade de coleção , tenta adicionar esses dois novos serviços B
ao controle de alterações. Mas falha porque ambas as novas B
entidades têm o mesmo Id = 0
:
// This line tries to add to change tracking two new B entities with the same Id = 0.
// As a result it fails.
db.Entry(a).Collection(x => x.S).IsModified = true;
Você pode ver no rastreamento da pilha que a estrutura da entidade tenta adicionar B
entidades ao IdentityMap
:
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetPropertyModified(IProperty property, Boolean changeState, Boolean isModified, Boolean isConceptualNull, Boolean acceptChanges)
at Microsoft.EntityFrameworkCore.ChangeTracking.NavigationEntry.SetFkPropertiesModified(InternalEntityEntry internalEntityEntry, Boolean modified)
at Microsoft.EntityFrameworkCore.ChangeTracking.NavigationEntry.SetFkPropertiesModified(Object relatedEntity, Boolean modified)
at Microsoft.EntityFrameworkCore.ChangeTracking.NavigationEntry.set_IsModified(Boolean value)
E a mensagem de erro também informa que ele não pode rastrear a B
entidade Id = 0
porque outra B
entidade com o mesmo Id
já está rastreada.
Como resolver este problema.
Para resolver esse problema, você deve excluir o código que cria B
entidades ao inicializar a S
coleção:
public ICollection<B> S { get; set; } = new List<B>();
Em vez disso, você deve preencher a S
coleção no local em que A
é criada. Por exemplo:
db.Add(new A {S = {new B(), new B()}});
Se você não usar o carregamento lento, deverá carregar explicitamente a S
coleção para adicionar seus itens ao rastreamento de alterações:
// Use eager loading, for example.
A a = db.Set<A>().Include(x => x.S).Single();
db.Entry(a).Collection(x => x.S).IsModified = true;
Por que não adiciona, em vez de anexar, as instâncias de B?
Em suma , eles são anexados a serem adicionados porque têm Detached
estado.
Depois de executar a linha de código
var a = db.Set<A>().Single();
instâncias criadas da entidade B
têm estado Detached
. Pode ser verificado usando o próximo código:
Console.WriteLine(db.Entry(a.S[0]).State);
Console.WriteLine(db.Entry(a.S[1]).State);
Então, quando você define
db.Entry(a).Collection(x => x.S).IsModified = true;
O EF tenta adicionar B
entidades para alterar o rastreamento. No código-fonte do EFCore, você pode ver que isso nos leva ao método InternalEntityEntry.SetPropertyModified com os próximos valores de argumento:
property
- uma das nossas B
entidades,
changeState = true
,
isModified = true
,
isConceptualNull = false
,
acceptChanges = true
.
Esse método com esses argumentos altera o estado dos Detached
B
atributos para Modified
e tenta iniciar o rastreamento deles (consulte as linhas 490 - 506). Como as B
entidades agora têm estado, Modified
isso as leva a serem anexadas (não adicionadas).