2009-05-22 7 views
6

Necesito establecer EntityObject's EntityKey. Sé su tipo y su valor de identificación. No quiero consultar la base de datos innecesariamente.EntityKey y ApplyPropertyChanges()

Esto funciona ...

// 
// POST: /Department/Edit/5 

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department Model) 
{ 
    Model.EntityKey = (from Department d in db.Department 
         where d.Id == id 
         select d).FirstOrDefault().EntityKey; 
    db.ApplyPropertyChanges(Model.EntityKey.EntitySetName, Model); 
    db.SaveChanges(); 
    return RedirectToAction("Index"); 
} 

Esta falla ...

// 
// POST: /Department/Edit/5 

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department Model) 
{ 
    String EntitySetName = db.DefaultContainerName + "." + Model.GetType().Name; 
    Model.EntityKey = new System.Data.EntityKey(EntitySetName, "Id", Model.Id); 
    db.ApplyPropertyChanges(Model.EntityKey.EntitySetName, Model); 
    db.SaveChanges(); 
    return RedirectToAction("Index"); 
} 

Los ApplyPropertyChanges() línea de falla con esta excepción:

El ObjectStateManager no lo hace contiene un ObjectStateEntry con un referencia a un objeto del tipo 'Sample.Models.Department'.

Las dos EntityKeys son iguales. ¿Por qué falla el segundo bloque de código? ¿Cómo puedo arreglarlo?

Respuesta

9

El motivo por el que falla su segundo bloque de código es porque EF no puede encontrar el objeto en ObjectStateManager; es decir, cuando extrae objetos del db los pone en el administrador de estado para que pueda rastrearlos, esto es similar a el patrón Identity Map. A pesar de tener una EntityKey, su objeto no está en el administrador de estado, por lo que EF no puede conservar los cambios. Puede evitar esto poniendo el objeto en el administrador de estado usted mismo, pero tiene que ser un poco astuto al respecto.

Esto funciona:

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department model) 
{ 
    var entitySetName = db.DefaultContainerName + "." + model.GetType().Name; 
    var entityKey = new System.Data.EntityKey(entitySetName, "Id", model.Id); 

    db.Attach(new Department{Id = id, EntityKey = entityKey}); 
    db.AcceptAllChanges(); 

    db.ApplyPropertyChanges(entitySetName, model); 
    db.SaveChanges(); 
} 

... pero no es muy limpio. Básicamente, esto es asociar un objeto 'vacío' con solo una clave de entidad, aceptando todos los cambios y luego llamando a ApplyPropertyChanges con los valores real actualizados reales.

Esto es lo mismo que en un método de extensión: esto debería funcionar para cualquier cosa que tenga una sola columna db para la clave principal. La única parte interesante de una llamada al método es que se necesita para decirle cómo encontrar la propiedad clave a través de un delegado como el segundo argumento al método de extensión:

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department model) 
{ 
    db.ApplyDetachedPropertyChanges(model, x => x.Id); 
    db.SaveChanges(); 
} 

y el método de extensión:

public static class EfExtensions 
{ 
    public static void ApplyDetachedPropertyChanges<T>(this ObjectContext db, T entity, Func<T, int> getIdDelegate) 
    where T : EntityObject 
    { 
    var entitySetName = db.DefaultContainerName + "." + entity.GetType().Name; 
    var id = getIdDelegate(entity); 
    var entityKey = new EntityKey(entitySetName, "Id", id); 

    db.Attach(new Department {Id = id, EntityKey = entityKey}); 
    db.AcceptAllChanges(); 

    db.ApplyPropertyChanges(entitySetName, entity); 
    } 
} 

Como el método de extensión llama a AcceptAllChanges, debe tener cuidado al llamar esto si está haciendo actualizaciones en varias entidades a la vez: podría "perder" fácilmente las actualizaciones si no tiene cuidado. Por lo tanto, este enfoque solo es realmente adecuado para escenarios simples de actualización, p. una gran cantidad de métodos de acción MVC :)

+0

+1 para el método de extensión. ¡Eso fue lindo! =) ¿Cómo debería cambiar el método si la tabla utiliza una PK de varias columnas? –

+0

Debería realizar una sobrecarga del método de extensión que tomó un IEnumerable de KeyValuePair para especificar la clave, y luego llamar a la sobrecarga del constructor correspondiente en EntityKey. Para mantener la seguridad de stype y evitar pasar cadenas en su KeyValuePair, podría pasar una serie de delegados 'clave-finding' junto con los valores para poner en las claves. –

+1

Basado en esta gran respuesta, lo mejoré un poco e hice mi propio pequeño método, que también publiqué como respuesta. Funciona para cualquier tipo de objeto de entidad y no usa delegados. –

4
public static class EfExtensions 
{ 
    public static void ApplyDetachedPropertyChanges<T>(this ObjectContext db, T entity, Func<T, int> getIdDelegate) 
    where T : EntityObject 
    { 
     var entitySetName = db.DefaultContainerName + "." + entity.GetType().Name; 

     T newEntity = Activator.CreateInstance<T>(); 
     newEntity.EntityKey = db.CreateEntityKey(entitySetName, entity); 

     Type t = typeof(T); 
     foreach(EntityKeyMember keyMember in newEntity.EntityKey.EntityKeyValues) { 
      PropertyInfo p = t.GetProperty(keyMember.Key); 
      p.SetValue(newEntity, keyMember.Value, null); 
     } 

     db.Attach(newEntity); 
     //db.AcceptAllChanges(); 

     db.ApplyPropertyChanges(entitySetName, entity); 
    } 
} 
+0

¡Gracias, estaba buscando algo como esto, y supliste la necesidad! 1 – camainc

+1

he tenido que añadir un "ToPlural()" método de extensión de cadena en "entity.GetType(). Nombre" porque mis entitysets están pluralizado por el generador de código. Aparte de eso, tu código funcionó perfectamente. – camainc

2

Trate de usar el código a continuación y hágamelo saber si le funciona.

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department Model) 
{ 
    using (var context = new EntityContext()) 
    { 
     try 
     { 
      Object entity = null; 
      IEnumerable<KeyValuePair<string, object>> entityKeyValues = 
       new KeyValuePair<string, object>[] { 
        new KeyValuePair<string, object>("DepartmentID", id) }; 

      // Create the key for a specific SalesOrderHeader object. 
      EntityKey key = new EntityKey("EntityContext.Deparment", 
                    entityKeyValues); 

      // Get the object from the context or the persisted store by its key. 
      if (context.TryGetObjectByKey(key, out entity)) 
      { 
       context.ApplyPropertyChanges(key.EntitySetName, Model); 
       context.SaveChanges(); 
      } 
      else 
      { 
       // log message if we need 
       //"An object with this key could not be found." 
      }     
     } 
     catch (EntitySqlException ex) 
     { 
      // log message 
     } 
    } 
}  
1

Tuve el mismo problema mientras trabajaba en otras páginas. No sé si este comentario será útil ... Pero finalmente descubrí que en mi objeto (el equivalente de su Departamento), mi "Id" no aparecía en la forma ...

Solo el hecho de agrégalo (mientras lo había eliminado ...) en mi formulario ha resuelto mi problema. (con el estilo = "visibilidad: oculto", para no verlo ...)

2

Mejorando la gran implementación de Steve Willcock, esta es mi sugerencia.

Utiliza Reflection (una parte de .NET) mucho más que el ejemplo original, para ahorrarle algo de código. También admite automáticamente cualquier tipo de clase de entidad, y no solo un "Departamento".

Además, se deshace del método obsoleto ApplyPropertyChanges, y utiliza el nuevo método ApplyCurrentValues.

El método

El método, básicamente, sólo utiliza la reflexión para obtener el valor de la propiedad "ID" de forma dinámica, y si es demasiado. Esto ahorra todas las molestias con un delegado.

public static void ApplyDetachedPropertyChanges<T>(this ObjectContext db, T entity) where T : EntityObject 
{ 
    PropertyInfo idProperty = typeof(T).GetProperty("Id"); 

    var entitySetName = db.DefaultContainerName + "." + entity.GetType().Name; 
    var id = idProperty.GetValue(entity, null); 
    var entityKey = new EntityKey(entitySetName, "Id", id); 

    Type type = entity.GetType(); 
    EntityObject obj = (EntityObject)Activator.CreateInstance(type); 

    idProperty.SetValue(obj, id, null); 
    obj.EntityKey = entityKey; 

    db.Attach(obj); 
    db.AcceptAllChanges(); 

    db.ApplyCurrentValues(entitySetName, entity); 
} 

Uso

Su uso es bastante simple también.

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department Model) 
{ 
    db.ApplyDetachedPropertyChanges(Model); 
    db.SaveChanges(); 
} 
Cuestiones relacionadas