La razón del error en el código proporcionado es la siguiente.
Cuando obtiene una entidad creada A
de la base de datos, su propiedad S
se inicializa con una colección que contiene dos nuevos registros B
. Id
de cada una de estas nuevas B
entidades 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 S
de entidad A
, no contiene B
entidades de la base de datos, porque DbContext Db
no utiliza carga diferida y no hay carga explícita de la colección S
. La entidad A
solo contiene nuevas B
entidades que se crearon durante la inicialización de la recopilación S
.
Cuando solicita IsModifed = true
un S
marco de entidad de recopilación , intenta agregar esas dos nuevas entidades B
al seguimiento de cambios. Pero falla porque ambas nuevas B
entidades 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 B
entidades 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 B
entidad Id = 0
porque otra B
entidad con la misma Id
ya está rastreada.
Cómo resolver este problema.
Para resolver este problema, debe eliminar el código que crea B
entidades al inicializar la S
recopilación:
public ICollection<B> S { get; set; } = new List<B>();
En su lugar, debe llenar la S
colección en el lugar donde A
se crea. Por ejemplo:
db.Add(new A {S = {new B(), new B()}});
Si no utiliza la carga diferida, debe cargar explícitamente la S
colecció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 Detached
estado.
Después de ejecutar la línea de código
var a = db.Set<A>().Single();
Las instancias creadas de entidad B
tienen 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 B
entidades,
changeState = true
,
isModified = true
,
isConceptualNull = false
,
acceptChanges = true
.
Este método con tales argumentos cambia el estado de las Detached
B
entidades a Modified
, y luego intenta comenzar a rastrearlas (véanse las líneas 490 - 506). Debido a que las B
entidades ahora tienen estado, Modified
esto los lleva a estar unidos (no agregados).