La raison de l'erreur dans le code fourni est la suivante.
Lorsque vous obtenez une entité créée à Apartir de la base de données, sa propriété Sest initialisée avec une collection qui contient deux nouveaux enregistrements B. Idde chacune de ces nouvelles Bentités est égal à 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 {} };
Après avoir exécuté la ligne de code, la var a = db.Set<A>().Single()collection Sd'entité Ane contient pas d' Bentités de la base de données, car DbContext Dbn'utilise pas de chargement différé et il n'y a pas de chargement explicite de la collection S. L'entité Acontient uniquement les nouvelles Bentités créées lors de l'initialisation de la collection S.
Lorsque vous appelez le framework d'entité de IsModifed = truecollecte, essayez Sd'ajouter ces deux nouvelles entités Bau suivi des modifications. Mais cela échoue car les deux nouvelles Bentités ont la même 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;
Vous pouvez voir dans la trace de la pile que le framework d'entité essaie d'ajouter des Bentités dans 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)
Et le message d'erreur indique également qu'il ne peut pas suivre l' Bentité avec Id = 0car une autre Bentité avec la même chose Idest déjà suivie.
Comment résoudre ce problème.
Pour résoudre ce problème, vous devez supprimer le code qui crée des Bentités lors de l'initialisation de la Scollection:
public ICollection<B> S { get; set; } = new List<B>();
Au lieu de cela, vous devez remplir la Scollection à l'endroit où Aest créé. Par exemple:
db.Add(new A {S = {new B(), new B()}});
Si vous n'utilisez pas le chargement différé, vous devez explicitement charger la Scollection pour ajouter ses éléments dans le suivi des modifications:
// Use eager loading, for example.
A a = db.Set<A>().Include(x => x.S).Single();
db.Entry(a).Collection(x => x.S).IsModified = true;
Pourquoi n'ajoute-t-il pas au lieu de joindre les instances de B?
Bref , ils sont attachés au lieu d'être ajoutés car ils ont de l' Detachedétat.
Après avoir exécuté la ligne de code
var a = db.Set<A>().Single();
les instances d'entité créées Bont un état Detached. Il peut être vérifié à l'aide du code suivant:
Console.WriteLine(db.Entry(a.S[0]).State);
Console.WriteLine(db.Entry(a.S[1]).State);
Ensuite, lorsque vous définissez
db.Entry(a).Collection(x => x.S).IsModified = true;
EF essaie d'ajouter des Bentités pour modifier le suivi. Du code source d' EFCore, vous pouvez voir que cela nous conduit à la méthode InternalEntityEntry.SetPropertyModified avec les valeurs d'argument suivantes:
property- une de nos Bentités,
changeState = true,
isModified = true,
isConceptualNull = false,
acceptChanges = true.
Cette méthode avec de tels arguments change l'état des Detached Bentites en Modified, puis essaie de commencer leur suivi (voir lignes 490 - 506). Parce que les Bentités ont maintenant un état, Modifiedcela les conduit à être attachées (non ajoutées).