Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EntityKey and ApplyPropertyChanges()

I need to set an EntityObject's EntityKey. I know its type and its id value. I don't want to query the database unnecessarily.

This works...

//
// 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");
}

This fails...

//
// 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");
}

The ApplyPropertyChanges() line fails with this exception:

The ObjectStateManager does not contain an ObjectStateEntry with a reference to an object of type 'Sample.Models.Department'.

The two EntityKeys are equal. Why does the second block of code fail? How can I fix it?

like image 300
Zack Peterson Avatar asked May 22 '09 14:05

Zack Peterson


1 Answers

The reason your second block of code fails is because EF can't find the object in the ObjectStateManager - i.e. when it pulls objects from the db it puts them in the state manager so it can track them - this is similar to the Identity Map pattern. Despite having an EntityKey, your object isn't in the state manager so EF is unable to persist the changes. You can get around this by putting the object into the state manager youself but you have be a bit sneaky about it.

This works:

[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();
}

... but it's not very clean. Basically this is attaching an 'empty' object with just an entity key, accepting all changes and then calling ApplyPropertyChanges with the actual real updated values.

Here's the same thing wrapped up in an extension method - this should work for anything that has uses a single db column for the primary key. The only interesting part of calling the method is that you need to tell it how to find the key property via a delegate as the second argument to the extension method:

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

and the extension method:

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);
  }
}

As the extension method is calling AcceptAllChanges, you'd need to be careful about calling this if you are doing updates on multiple entities at once - you could easily 'lose' updates if you aren't careful. Hence this approach is only really suitable for simple update scenarios - e.g. a lot of MVC action methods :)

like image 89
Steve Willcock Avatar answered Sep 18 '22 11:09

Steve Willcock