La razón del error en el código proporcionado es la siguiente.
Cuando obtiene una entidad creada Ade la base de datos, su propiedad Sse inicializa con una colección que contiene dos nuevos registros B. Idde cada una de estas nuevas Bentidades es 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 {} };
Después de ejecutar la línea de var a = db.Set<A>().Single()colección de código Sde entidad A, no contiene Bentidades de la base de datos, porque DbContext Dbno utiliza carga diferida y no hay carga explícita de la colección S. La entidad Asolo contiene nuevas Bentidades que se crearon durante la inicialización de la recopilación S.
Cuando solicita IsModifed = trueun Smarco de entidad de recopilación , intenta agregar esas dos nuevas entidades Bal seguimiento de cambios. Pero falla porque ambas nuevas Bentidades tienen lo mismo 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;
Puede ver en el seguimiento de la pila que el marco de la entidad intenta agregar Bentidades en 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)
Y el mensaje de error también dice que no puede rastrear la Bentidad Id = 0porque otra Bentidad con la misma Idya está rastreada.
Cómo resolver este problema.
Para resolver este problema, debe eliminar el código que crea Bentidades al inicializar la Srecopilación:
public ICollection<B> S { get; set; } = new List<B>();
En su lugar, debe llenar la Scolección en el lugar donde Ase crea. Por ejemplo:
db.Add(new A {S = {new B(), new B()}});
Si no utiliza la carga diferida, debe cargar explícitamente la Scolección para agregar sus elementos al seguimiento de cambios:
// 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 qué no agrega en lugar de adjuntar las instancias de B?
En resumen , se adjuntan en lugar de ser agregados porque tienen Detachedestado.
Después de ejecutar la línea de código
var a = db.Set<A>().Single();
Las instancias creadas de entidad Btienen estado Detached. Se puede verificar usando el siguiente código:
Console.WriteLine(db.Entry(a.S[0]).State);
Console.WriteLine(db.Entry(a.S[1]).State);
Entonces cuando configuras
db.Entry(a).Collection(x => x.S).IsModified = true;
EF intenta agregar B entidades para cambiar el seguimiento. Desde el código fuente de EFCore , puede ver que esto nos lleva al método InternalEntityEntry.SetPropertyModified con los siguientes valores de argumento:
property- una de nuestras Bentidades,
changeState = true,
isModified = true,
isConceptualNull = false,
acceptChanges = true.
Este método con tales argumentos cambia el estado de las Detached Bentidades a Modified, y luego intenta comenzar a rastrearlas (véanse las líneas 490 - 506). Debido a que las Bentidades ahora tienen estado, Modifiedesto los lleva a estar unidos (no agregados).