La raison de l'erreur dans le code fourni est la suivante.
Lorsque vous obtenez une entité créée à A
partir de la base de données, sa propriété S
est initialisée avec une collection qui contient deux nouveaux enregistrements B
. Id
de chacune de ces nouvelles B
entité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 S
d'entité A
ne contient pas d' B
entités de la base de données, car DbContext Db
n'utilise pas de chargement différé et il n'y a pas de chargement explicite de la collection S
. L'entité A
contient uniquement les nouvelles B
entités créées lors de l'initialisation de la collection S
.
Lorsque vous appelez le framework d'entité de IsModifed = true
collecte, essayez S
d'ajouter ces deux nouvelles entités B
au suivi des modifications. Mais cela échoue car les deux nouvelles B
entité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 B
entité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' B
entité avec Id = 0
car une autre B
entité avec la même chose Id
est déjà suivie.
Comment résoudre ce problème.
Pour résoudre ce problème, vous devez supprimer le code qui crée des B
entités lors de l'initialisation de la S
collection:
public ICollection<B> S { get; set; } = new List<B>();
Au lieu de cela, vous devez remplir la S
collection à l'endroit où A
est 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 S
collection 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 B
ont 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 B
entité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 B
entités,
changeState = true
,
isModified = true
,
isConceptualNull = false
,
acceptChanges = true
.
Cette méthode avec de tels arguments change l'état des Detached
B
entites en Modified
, puis essaie de commencer leur suivi (voir lignes 490 - 506). Parce que les B
entités ont maintenant un état, Modified
cela les conduit à être attachées (non ajoutées).