La ragione dell'errore nel codice fornito è la seguente.
Quando si ottiene un'entità creata Adal database, la sua proprietà Sviene inizializzata con una raccolta che contiene due nuovi record B. Iddi ciascuna di queste nuove Bentità è 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 Sdell'entità Anon contiene Bentità dal database, poiché DbContext Dbnon utilizza il caricamento lento e non viene caricato esplicitamente la raccolta S. L'entità Acontiene solo nuove Bentità create durante l'inizializzazione della raccolta S.
Quando si chiama il framework di entità di IsModifed = trueraccolta S, si tenta di aggiungere queste due nuove entità Bal rilevamento delle modifiche. Ma fallisce perché entrambe le nuove Bentità 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 Bentità 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 Bun'entità con Id = 0perché un'altra Bentità con la stessa Idè già tracciata.
Come risolvere questo problema.
Per risolvere questo problema è necessario eliminare il codice che crea Bentità durante l'inizializzazione della Sraccolta:
public ICollection<B> S { get; set; } = new List<B>();
Dovresti invece riempire la Sraccolta 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 Sraccolta 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 Detachedstato.
Dopo aver eseguito la riga di codice
var a = db.Set<A>().Single();
le istanze create dell'entità Bhanno 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 Bentità 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 Bentità,
changeState = true,
isModified = true,
isConceptualNull = false,
acceptChanges = true.
Questo metodo con tali argomenti modifica lo stato delle Detached Bentità in Modified, quindi tenta di avviare il tracciamento per esse (vedere le righe 490 - 506). Poiché le Bentità ora hanno uno stato, Modifiedciò li porta ad essere collegati (non aggiunti).