5

Tengo un problema con el código del Entity Framework primero en MVC3. Estoy llegando a esta excepción:Código de Entity Framework: Primero: "ObjectStateManager no puede rastrear múltiples objetos con la misma clave".

Ya existe un objeto con la misma clave en el ObjectStateManager. ObjectStateManager no puede rastrear múltiples objetos con la misma clave .

Esto se aborda muchas veces en SO, pero estoy teniendo problemas para utilizar cualquiera de las soluciones sugeridas en mi situación.

Aquí es un ejemplo de código:

FestORM.SaleMethod method = new FestORM.SaleMethod 
{ 
    Id = 2, 
    Name = "Test Sale Method" 
}; 
FestContext context = new FestContext(); 

//everything works without this line: 
string thisQueryWillMessThingsUp = 
    context.SaleMethods.Where(m => m.Id == 2).Single().Name; 

context.Entry(method).State = System.Data.EntityState.Modified; 
context.SaveChanges(); 

Editado para aclarar: Estoy intentando actualizar un objeto que ya existe en la base de datos.

Todo funciona bien sin la consulta indicada en el código. En mi aplicación, mi controlador está creando instancias del contexto, y ese mismo contexto se pasa a varios repositorios que usa el controlador, por lo que no puedo usar simplemente un contexto diferente para la operación de consulta inicial. Intenté eliminar a la entidad del seguimiento en ObjectStateManager, pero parece que tampoco puedo llegar a ninguna parte con eso. Estoy tratando de encontrar una solución que funcione para ambas condiciones: a veces voy a actualizar un objeto que es rastreado por el ObjectStateManager, y algunas veces todavía no ha sido rastreado.

Fwiw, mis funciones de almacén reales tienen este aspecto, al igual que el código anterior:

public void Update(T entity) 
{ 
    //works ONLY when entity is not tracked by ObjectStateManager 
    _context.Entry(entity).State = System.Data.EntityState.Modified; 
} 

public void SaveChanges() 
{ 
    _context.SaveChanges(); 
} 

¿Alguna idea? He estado luchando esto durante demasiado tiempo ...

Respuesta

12

El problema es que esta consulta

string thisQueryWillMessThingsUp = 
    context.SaleMethods.Where(m => m.Id == 2).Single().Name; 

trae una instancia de la entidad SaleMethod en el contexto y luego el código

context.Entry(method).State = System.Data.EntityState.Modified; 

concede una instancia diferente al contexto.Ambas instancias tienen la misma clave primaria, por lo que EF piensa que está intentando unir dos entidades diferentes con la misma clave para el contexto. No sabe que se supone que ambos son la misma entidad.

Si por alguna razón sólo tiene que consultar para el nombre, pero no quiere plasmar en la realidad la entidad completa en el contexto, entonces usted puede hacer esto:

string thisQueryWillMessThingsUp =   
    context.SaleMethods.Where(m => m.Id == 2).AsNoTracking().Single().Name; 

Si lo que usted está atando que hacer es actualizar una entidad existente y que tiene valores para todas las propiedades asignadas de esa entidad, entonces la cosa más sencilla de hacerlo es no ejecutar la consulta y sólo tiene que utilizar:

context.Entry(method).State = System.Data.EntityState.Modified; 

Si no quiere actualizar todas las propiedades, posiblemente porque no tiene valores para todas las propiedades, luego consulta para la entidad y las propiedades de configuración en él antes de llamar a SaveChanges es un enfoque aceptable. Hay varias maneras de hacer esto dependiendo de sus requisitos exactos. Una forma es utilizar el método de la propiedad, algo así:

var salesMethod = context.SaleMethods.Find(2); // Basically equivalent to your query 
context.Entry(salesMethod).Property(e => e.Name).CurrentValue = newName; 
context.Entry(salesMethod).Property(e => e.SomeOtherProp).CurrentValue = newOtherValue; 
context.SaveChanges(); 

Estas entradas de blog contienen alguna información adicional que pueda ser útil:

http://blogs.msdn.com/b/adonet/archive/2011/01/29/using-dbcontext-in-ef-feature-ctp5-part-4-add-attach-and-entity-states.aspx

http://blogs.msdn.com/b/adonet/archive/2011/01/30/using-dbcontext-in-ef-feature-ctp5-part-5-working-with-property-values.aspx

+0

Gracias. (No me permite votar su respuesta porque soy nuevo.) El problema con esto es que no sé cuándo estoy haciendo una consulta que precede a una actualización futura. La consulta puede estar enterrada en algún lugar de la capa de servicio, y finalmente, cuando voy a hacer una actualización en la misma entidad que se consultó, presiono el error. ¿Hay alguna forma de eliminar el objeto del seguimiento del contexto o de localizar el objeto que el contexto rastrea y comunicar al contexto que efectivamente estoy actualizando este objeto en cuestión? He investigado algunos en esta dirección, pero aún no tengo suerte. – Josh

+0

Depende de cuál sea la intención de la consulta. Si la consulta pretende incluir una entidad en el contexto para que pueda ser actualizada y guardada, lo que es habitual, entonces debe diseñar para eso, posiblemente asegurándose antes de guardar que la entidad ingresa, si no lo ha hecho. sido traído antes Si la intención de la consulta es obtener alguna propiedad de la entidad de la base de datos por algún motivo sin incluir a la entidad en el contexto, entonces use AsNoTracking en esa consulta como se muestra arriba. –

+0

Para agregar a mi comentario anterior, si quiere asegurarse de que la entidad ha sido ingresada, entonces use el método Find antes de actualizar las propiedades en la entidad y guardarlas. También puede usar una consulta en lugar de Buscar: de manera predeterminada, la consulta solo mostrará la entidad si aún no está en el contexto. Pero Find es más eficiente. –

1

La respuesta obvia sería que su realidad no salvar el método objeto de la base de datos antes de llamar:

//everything works without this line: 
string thisQueryWillMessThingsUp = context.SaleMethods.Where(m => m.Id == 2).Single().Name; 

Sin embargo, creo que tal vez esta es solo un poco un código que dejaste fuera. ¿Qué pasa si haces que tus entidades hereden de una clase abstracta, es decir.

public abstract class BaseClass 
{ 
    public int Id { get; set; } 
} 

luego actualizar su repositorio

public class Repository<T> where T : BaseClass 
{ 
..... 
    public void Update(T entity) 
    {   
     _context.Entry(entity).State = entity.Id == 0 ? System.Data.EntityState.Added : System.Data.EntityState.Modified; 
    } 
} 

También es posible que desee no ajustar el ID de su SaleMethod Y que se genera por la base de datos. El problema también podría ser porque SaleMethod Object en la base de datos tiene Id of 2 y luego intenta agregar otro objeto SaleMethod con Id 2. El error que ve se debe a tratar de agregar otro objeto SaleMethod con ID de 2 al ObjectStateManager.

+0

Gracias por la respuesta. Sí, los ID son generados por la base de datos con seguridad. Esta es específicamente una operación de ** actualización **. Dejé fuera la parte donde el objeto se guarda inicialmente (podría haber sido guardado inicialmente por otro código hace años, etc.). La cuestión es que la actualización funciona bien, siempre que no realice una consulta primero. La ejecución de la consulta aparentemente la agrega al seguimiento de ObjectStateManager, momento en el que mi actualización grazna. – Josh

Cuestiones relacionadas