En el código de ejemplo a continuación, obtengo la siguiente excepción al hacer db.Entry(a).Collection(x => x.S).IsModified = true
:
System.InvalidOperationException: 'La instancia del tipo de entidad' B 'no se puede rastrear porque ya se está rastreando otra instancia con el valor clave' {Id: 0} '. Al adjuntar entidades existentes, asegúrese de que solo se adjunte una instancia de entidad con un valor clave dado.
¿Por qué no agrega en lugar de adjuntar las instancias de B?
Curiosamente, la documentación de IsModified
no especifica InvalidOperationException
como una posible excepción. Documentación inválida o un error?
Sé que este código es extraño, pero lo escribí solo para comprender cómo funciona ef core en algunos casos extraños de egde. Lo que quiero es una explicación, no una solución.
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
public class A
{
public int Id { get; set; }
public ICollection<B> S { get; set; } = new List<B>() { new B {}, new B {} };
}
public class B
{
public int Id { get; set; }
}
public class Db : DbContext {
private const string connectionString = @"Server=(localdb)\mssqllocaldb;Database=Apa;Trusted_Connection=True";
protected override void OnConfiguring(DbContextOptionsBuilder o)
{
o.UseSqlServer(connectionString);
o.EnableSensitiveDataLogging();
}
protected override void OnModelCreating(ModelBuilder m)
{
m.Entity<A>();
m.Entity<B>();
}
}
static void Main(string[] args)
{
using (var db = new Db()) {
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
db.Add(new A { });
db.SaveChanges();
}
using (var db = new Db()) {
var a = db.Set<A>().Single();
db.Entry(a).Collection(x => x.S).IsModified = true;
db.SaveChanges();
}
}
}
fuente
Respuestas:
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 propiedadS
se inicializa con una colección que contiene dos nuevos registrosB
.Id
de cada una de estas nuevasB
entidades es igual a0
.Después de ejecutar la línea de
var a = db.Set<A>().Single()
colección de códigoS
de entidadA
, no contieneB
entidades de la base de datos, porqueDbContext Db
no utiliza carga diferida y no hay carga explícita de la colecciónS
. La entidadA
solo contiene nuevasB
entidades que se crearon durante la inicialización de la recopilaciónS
.Cuando solicita
IsModifed = true
unS
marco de entidad de recopilación , intenta agregar esas dos nuevas entidadesB
al seguimiento de cambios. Pero falla porque ambas nuevasB
entidades tienen lo mismoId = 0
:Puede ver en el seguimiento de la pila que el marco de la entidad intenta agregar
B
entidades enIdentityMap
:Y el mensaje de error también dice que no puede rastrear la
B
entidadId = 0
porque otraB
entidad con la mismaId
ya está rastreada.Cómo resolver este problema.
Para resolver este problema, debe eliminar el código que crea
B
entidades al inicializar laS
recopilación:En su lugar, debe llenar la
S
colección en el lugar dondeA
se crea. Por ejemplo:Si no utiliza la carga diferida, debe cargar explícitamente la
S
colección para agregar sus elementos al seguimiento de cambios:En resumen , se adjuntan en lugar de ser agregados porque tienen
Detached
estado.Después de ejecutar la línea de código
Las instancias creadas de entidad
B
tienen estadoDetached
. Se puede verificar usando el siguiente código:Entonces cuando configuras
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 nuestrasB
entidades,changeState = true
,isModified = true
,isConceptualNull = false
,acceptChanges = true
.Este método con tales argumentos cambia el estado de las
Detached
B
entidades aModified
, y luego intenta comenzar a rastrearlas (véanse las líneas 490 - 506). Debido a que lasB
entidades ahora tienen estado,Modified
esto los lleva a estar unidos (no agregados).fuente
S
debe cargarse explícitamente, porque el código provisto no usa carga diferida. Por supuesto, EF guardóB
entidades creadas previamente en la base de datos. Pero la línea de códigoA a = db.Set<A>().Single()
solo carga entidadesA
sin entidades en la colecciónS
. Para cargar la colección, seS
debe utilizar una carga ansiosa. Cambiaré mi respuesta para incluir explícitamente la respuesta a la pregunta "¿Por qué no agrega en lugar de adjuntar las instancias de B?".