How do I edit an entity using the Entity Framework Code First approach?
I have been tearing my hair out over this for days and before I go completely bald it's time to ask all the people smarter than me how to do this.
I am using Entity Framework 4 with the Code First CTP 5 and MVC 3.
开发者_开发知识库The exception message right now is "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key."
First up here is the controller the edit form is posted to:
public ActionResult Save(ClientEntity postedClient)
{
try
{
if (ModelState.IsValid)
{
Base.clientInterface.Save(postedClient);
return RedirectToAction("Index");
}
}
catch (Exception)
{
throw;
}
SetupManageData(null, postedClient);
return View("Manage");
}
The Save method on the client interface is this:
public void Save(ClientEntity theClient)
{
SetContext();
if (theClient.clientId == 0)
this.pContext.Clients.Add(theClient);
else
{
ClientEntity existingClient = GetSingle(theClient.clientId); // Get the existing entity from the data store.
// PseudoCode: Merge existingClient and theClient - Can this be done without using ObjectStateManager?
// PseudoCode: Attach merged entity to context so that SaveChanges will update it in the database - is this correct?
}
this.pContext.SaveChanges();
}
private void SetContext()
{
if (this.pContext == null)
this.pContext = new PersistanceContext();
}
Persistance context is the DBContext and looks like this:
public class PersistanceContext : DbContext
{
public DbSet<ClientEntity> Clients { get; set; }
}
What is the lifestyle of clientInterface? is it a singleton or something that keeps it alive across multiple requests?
My guess is that it holds a live instance of a database context that was used to fetch the entity in the GET request and when the POST tries to (re)add a client entity to the context the old one is still there and they conflict.
Try destroying the object that is behind the clientInterface with every request. Maybe use a DI container that supports per-webrequest lifestyles so you don't have to worry about it.
I hope my guess was right and this is of help.
This should work.
if (theClient.clientId == 0)
{
this.pContext.Clients.Add(theClient);
}
else
{
ClientEntity existingClient = this.pContext.Clients.Single(o => o.ClientId == theClient.ClientId);
// map properties
existingClient.Name = theClient.name;
// ....
}
this.pContext.SaveChanges();
[Edit]
It's easier (IMHO) to split the creation & editing of objects into 2 seperate views and to avoid the mapping of the properties I use TryUpdateModel.
[HttpPost]
public ViewResult Edit(int clientID, FormCollection collection)
{
var client = pContext.Clients.SingleOrDefault(o => o.ID == clientID);
if(!TryUpdateModel(client, collection))
{
ViewBag.UpdateError = "Update Failure";
}
else
{
db.SubmitChanges();
}
return View("Details", client);
}
[HttpPost]
public ViewResult Create(FormCollection collection)
{
var client = new Client();
if(!TryUpdateModel(client, collection))
{
ViewBag.UpdateError = "Create Failure";
}
else
{
db.Clients.Add(client);
db.SubmitChanges();
}
return View("Details", client);
}
精彩评论