La ragione dell'errore nel codice fornito è la seguente.
Quando si ottiene un'entità creata A
dal database, la sua proprietà S
viene inizializzata con una raccolta che contiene due nuovi record B
. Id
di ciascuna di queste nuove B
entità è uguale 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 {} };
Dopo aver eseguito la riga di var a = db.Set<A>().Single()
raccolta codice S
dell'entità A
non contiene B
entità dal database, poiché DbContext Db
non utilizza il caricamento lento e non viene caricato esplicitamente la raccolta S
. L'entità A
contiene solo nuove B
entità create durante l'inizializzazione della raccolta S
.
Quando si chiama il framework di entità di IsModifed = true
raccolta S
, si tenta di aggiungere queste due nuove entità B
al rilevamento delle modifiche. Ma fallisce perché entrambe le nuove B
entità hanno lo stesso 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;
Dalla traccia dello stack è possibile vedere che il framework di entità tenta di aggiungere B
entità in 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 il messaggio di errore dice anche che non è possibile tracciare B
un'entità con Id = 0
perché un'altra B
entità con la stessa Id
è già tracciata.
Come risolvere questo problema.
Per risolvere questo problema è necessario eliminare il codice che crea B
entità durante l'inizializzazione della S
raccolta:
public ICollection<B> S { get; set; } = new List<B>();
Dovresti invece riempire la S
raccolta nel luogo in cui A
è stata creata. Per esempio:
db.Add(new A {S = {new B(), new B()}});
Se non si utilizza il caricamento lento, è necessario caricare esplicitamente la S
raccolta per aggiungere i relativi elementi nel rilevamento delle modifiche:
// Use eager loading, for example.
A a = db.Set<A>().Include(x => x.S).Single();
db.Entry(a).Collection(x => x.S).IsModified = true;
Perché non aggiunge invece di collegare le istanze di B?
In breve , sono associati all'istanza di essere aggiunti perché hanno Detached
stato.
Dopo aver eseguito la riga di codice
var a = db.Set<A>().Single();
le istanze create dell'entità B
hanno stato Detached
. Può essere verificato utilizzando il codice seguente:
Console.WriteLine(db.Entry(a.S[0]).State);
Console.WriteLine(db.Entry(a.S[1]).State);
Quindi quando si imposta
db.Entry(a).Collection(x => x.S).IsModified = true;
EF cerca di aggiungere B
entità per cambiare tracciamento. Dal codice sorgente di EFCore puoi vedere che questo ci porta al metodo InternalEntityEntry.SetPropertyModified con i seguenti valori dell'argomento:
property
- una delle nostre B
entità,
changeState = true
,
isModified = true
,
isConceptualNull = false
,
acceptChanges = true
.
Questo metodo con tali argomenti modifica lo stato delle Detached
B
entità in Modified
, quindi tenta di avviare il tracciamento per esse (vedere le righe 490 - 506). Poiché le B
entità ora hanno uno stato, Modified
ciò li porta ad essere collegati (non aggiunti).