Un escenario común es el siguiente:
"Al enviar un formulario web, creo una entidad Pedido con los datos proporcionados por el usuario y lo entrego a mi capa de negocios o servicios para que se valide y persista en el almacén de datos."
En este escenario la entidad Pedido enviada desde la página web (o enviada desde un Web Service), se encuentra en estado Deattached. Para que podamos persistir sus cambios necesitamos asociarla a un contexto y luego llamar al método SaveChanges().
Si la entidad aún no existe en el almacén de datos, debemos agregarla. Esto es muy simple, usando los métodos AddTo* generados en el modelo:
context.AddToOrders(order);
context.SaveChanges();
En cambio si la entidad ya existe en el almacén de datos, entonces debemos asociarla al contexto y guardar los cambios. Esta tarea parece ser simple y podríamos intentar resolverla de la siguiente manera:
context.AttachTo("Orders", order);
context.SaveChanges();
Sin embargo, esto no funciona, pues el objeto order se asocia exitosamente al contexto pero en el estado Unchanged. Luego el método SaveChanges() no persiste ningún cambio.
La forma de resolver este problema es recuperando la entidad original desde el almacén de datos, aplicar los cambios, y volver a guardarla.
Buscando en la web encontré diferentes enfoques para resolver este problema. Muchos utilizan Reflection para recorrer todas las propiedades y asignar las que han cambiado. La que más me ha interesado la encontré en el blog de Cesar de la Torre.
Se trata de un extension method llamado AttachUpdated() aplicado al ObjectContext. Me he tomado el atrevimiento de renombrar le método AttacheUpdated() por UpdateObject(), pues me pareció más consistente con otros métodos que ya existen en el ObjectContext como AddObject() y DeleteObject(). El código es el siguiente:
public static void UpdateObject(this ObjectContext context, string entitySetName, EntityObject entity)
{
var key = context.CreateEntityKey(entitySetName, entity);
object originalEntity;
if (context.TryGetObjectByKey(key, out originalEntity))
{
context.ApplyPropertyChanges(entitySetName, entity);
context.ApplyReferencePropertyChanges(entity, (IEntityWithRelationships)originalEntity);
}
else
{
throw new ObjectNotFoundException();
}
}
Este método primero crea la EntityKey asociada al objeto usando el método CreateEntityKey() y luego intenta recuperar el objeto desde el almacén de datos usando el método TryGetObjectByKey(). A continuación aplica los cambios usando el método ApplyPropertyChanges() y luego llama al método ApplyReferencePropertyChanges() que se encarga de aplicar los cambios a las entidades relacionadas. Este último método se trata de otro extension method:
private static void ApplyReferencePropertyChanges(this ObjectContext context, IEntityWithRelationships newEntity, IEntityWithRelationships oldEntity)
{
foreach (var relatedEnd in oldEntity.RelationshipManager.GetAllRelatedEnds())
{
var oldRef = relatedEnd as EntityReference;
if (oldRef != null)
{
var newRef = newEntity.RelationshipManager.GetRelatedEnd(oldRef.RelationshipName, oldRef.TargetRoleName) as EntityReference;
if (newRef != null) oldRef.EntityKey = newRef.EntityKey;
}
}
}
Aquí no solo se resuelve el problema de aplicar los cambios a las propiedades del objeto en cuestión sino también los cambios en sus objetos relacionados.
Luego de crear estos extensions methods, el código final para actualizar una entidad desconectada quedaría como:
context.UpdateObject("Orders", order); //Extension method
context.SaveChanges();
He probado esto y funcionó para mí. Espero que también les sea de utilidad.
Saludos, Gus