Estoy utilizando Entity Framework 4.3.1 en un proyecto, usando primero el código y la API DbContext. Mi aplicación es una aplicación de n niveles donde pueden aparecer objetos desconectados de un cliente. Estoy usando SQL Server 2008 R2, pero pronto cambiaré a SQL Azure. Me encuentro con un problema que parece que no puedo resolver.Guardar objetos individuales con el código de Entity Framework primero
Imagínese que tengo unas cuantas clases:
class A {
// Random stuff here
}
class B {
// Random stuff here
public A MyA { get; set; }
}
class C {
// Random stuff here
public A MyA { get; set; }
}
Por defecto, EF funciona con gráficos de objetos. Por ejemplo, si tengo una instancia de B que encapsula una instancia de A y llamo a myDbSet.Add(myB);
, también marcará la instancia de A como agregada (suponiendo que aún no se esté rastreando).
Tengo un escenario en mi aplicación donde tengo que ser explícito sobre qué objetos persisten en la base de datos, en lugar de hacer un seguimiento de gráficos de objetos enteros. El orden de las operaciones es la siguiente:
A myA = new A(); // Represents something already in DB that doesn't need to be udpated.
C myC = new C() { // Represents something already in DB that DOES need to be updated.
A = myA;
}
B myB0 = new B() { // Not yet in DB.
A = myA;
}
B myB1 = new B() { // Not yet in DB.
A = myA;
}
myDbSetC.Attach(myC);
context.Entry(myC).State = Modified;
myDbSetB.Add(myB0); // Tries to track myA with a state of Added
myDbSetB.Add(myB1);
context.SaveChanges();
En este punto consigo diciendo un error AcceptChanges cannot continue because the object's key values conflict with another object in the ObjectStateManager. Make sure that the key values are unique before calling AcceptChanges.
Creo que esto se debe a que llamar a poner en myB0 marca la instancia de A como está añadiendo, que entra en conflicto con la instancia de A ya siendo rastreado
Idealmente podría hacer algo como llamar al myDbSet.AddOnly(myB)
, pero obviamente no tenemos esa opción.
He intentado varias soluciones:
Intento # 1: En primer lugar, intentaron crear un método de ayuda para evitar Mya se añada una segunda vez.
private void MarkGraphAsUnchanged<TEntity>(TEntity entity) where TEntity : class {
DbEntityEntry entryForThis = this.context.Entry<TEntity>(entity);
IEnumerable<DbEntityEntry> entriesItWantsToChange = this.context.ChangeTracker.Entries().Distinct();
foreach (DbEntityEntry entry in entriesItWantsToChange) {
if (!entryForThis.Equals(entry)) {
entry.State = System.Data.EntityState.Unchanged;
}
}
}
...
myDbSetB.Add(myB0);
MarkGraphAsUnchanged(myB0);
Aunque esto resuelve el problema de que se trata de añadir Mya, todavía provoca violaciónes clave dentro de la ObjectStateManager.
Intento # 2: Intenté hacer lo mismo que el anterior, pero estableciendo el estado en Independiente en lugar de Sin cambios. Esto funciona para guardar, pero insiste en establecer myB0.A = null
, que tiene otros efectos adversos en mi código.
Intento n. ° 3: Utilicé un TransactionScope alrededor de todo el DbContext. Sin embargo, incluso al llamar al SaveChanges()
entre cada Attach()
y Add()
, el rastreador de cambios no vacía sus entradas rastreadas, así que tengo el mismo problema que en el intento # 1.
Intento # 4: continué con el TransactionScope, excepto utilicé un patrón repositorio/DAO e internamente crear un nuevo DbContext y llamar SaveChanges()
para cada operación distinta que hago. En este caso, recibí el error 'La actualización de la tienda, la inserción o la instrucción de eliminación afectó a un número inesperado de filas'. Cuando se utiliza el Analizador de SQL, encuentro que cuando se llama a SaveChanges()
en la segunda operación que hice (la primera Add()
), lo que realmente envía el SQL UPDATE
a la base de datos de la primera operación una segunda vez - pero no lo hace cambiar cualquier fila Esto se siente como un error en Entity Framework para mí.
Intento n. ° 5: En lugar de utilizar TransactionScope, decidí utilizar solo DbTransaction. Todavía creo contextos múltiples, pero paso una EntityConnection precompilada a cada nuevo contexto a medida que se crea (mediante el almacenamiento en caché y la apertura manual de la EntityConnection creada por el primer contexto). Sin embargo, cuando hago esto, el segundo contexto ejecuta un inicializador que he definido, aunque ya se hubiera ejecutado cuando la aplicación se inició por primera vez. En un entorno de desarrollo tengo este seeding algunos datos de prueba, y en realidad agota el tiempo para un bloqueo de base de datos en una tabla mi primer Attach()
modificado (pero todavía está bloqueado debido a que la transacción aún está abierta).
¡Ayuda! Intenté todo lo que se me ocurrió y, salvo por la refacturación completa de mi aplicación para no utilizar las propiedades de navegación o el uso de DAO construidos manualmente para hacer las instrucciones INSERTAR, ACTUALIZAR y ELIMINAR, me siento perdido. ¡Parece que debe haber una forma de obtener los beneficios de Entity Framework para el mapeo O/R pero aún así controlar manualmente las operaciones dentro de una transacción!
¿Has intentado solo conectar myA? Tendrás que hacer esto antes de adjuntar cualquier otra cosa. – cadrell0