I am developing an ASP.Net MVC 3 Web application using Entity Framework 4.1, however, I am having an issue with updating the navigation entity of an entity.
I have an Entity called Shift
public partial class Shift
{
public Shift()
{
this.Locations = new HashSet<ShiftLocation>();
}
public int shiftID { get; set; }
public string shiftTitle { get; set; }
public virtual ICollection<ShiftLocation> Locations { get; set; }
}
In my Shift Controller, I have two HttpPost methods to create and edit a Shift.
[HttpPost]
public ActionResult CreateShift(ViewModelShift model)
{
if (ModelState.IsValid)
{
Shift shift = new Shift();
shift = Mapper.Map<ViewModelShift, Shift>(model);
//Create new shift location and add it to the newly created Shift
ShiftLocation location = new ShiftLocation();
location.locationID = model.locationID;
shift.Locations.Add(location);
_shiftService.AddShift(shift);
return RedirectToAction("Index");
}
}
[HttpPost]
public ActionResult EditShift(ViewModelShift model)
{
if (ModelState.IsValid)
{
Shift shift = new Shift();
shift = Mapper.Map<ViewModelShift, Shift>(model);
ShiftLocation location = new ShiftLocation();
//location = Mapper.Map<ViewModelShift, ShiftLocation>(model);
location.shiftID = model.shiftID;
location.locationID = model.locationID;
shift.Locations.Add(location);
_shiftService.UpdateShift(shift);
return RedirectToAction("Index");
}
}
The CreateShift method works fine, ie, inserts a record into the database for a new Shift and also a new record for a Shift Location within another table. The EditShift method however, only updates the Shift record, but it does not update the Shift Location record even though I have assigned the shiftID and the new locationID.
Any info on how to correct this would be much appreciated.
Thank you.
EDIT
My ShiftLocation class is as follows
public partial class ShiftLocation
{
public int shiftLocationID { get; set; }
public int shiftID { get; set; }
public int locationID { get; set; }
public virtual Shift Shift { get; set; }
}
The UpdateShift method within the ShiftService class is as follows
public void UpdateShift(Shift item)
{
_UoW.Shifts.Update(item);
_UoW.Commit();
}
And this then calls the Update method in my Generic Repository
public void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
2nd Edit
Following on from Slauma’s answer I edited my EditShift Post Action to the following
if (ModelState.IsValid)
{
//Shift shift = new Shift();
Shift shift = _shiftService.GetShiftByID(model.shiftID);
ShiftLocation shiftLocation = shift.Locations.Where(s => s.shiftID == model.shiftID).Single();
shift = Mapper.Map<ViewModelShift, Shift>(model);
shiftLocation.locationID = model.locationID;
shift.Locations.Add(shiftLocation);
_shiftService.UpdateShift(shift);
return RedirectToAction("Index");
}
My problem now is that when Update Method in my Generic Repository is called, the following error occurs
An object with the same key already exists in the ObjectStateManager.
The ObjectStateManager cannot track multiple objects with the same key
Now, I now why this is happening, because in my EditPost action I am retrieving an instance of the Shift, then in the Update Method in the Generic Repository I am trying to attach the same Shift.
I am just getting really confused now. My Generic Repositro looks fine as I copied it from the Microsoft MVC tutorial mentioned at the top of this post.
Again any help on how to get this simple update working would be much appreciated.
Refering to your comment…
… I would try this (omitting your repository structure):
Setting the state of the
shifttoModifieddoes not work because it doesn’t affect any related entities. The location would still be in stateUnchanged. You could also set the state for the location toModified, but it looks like you don’t have the primary key propertyshiftLocationIDin your ViewModel. So, the only way to find the correct location to update is loading it via the navigation property of the shift (=Includein the example above).Edit
The code in your 2nd Edit doesn’t work because AutoMapper creates a new Instance of
shiftand later you attach this shift to the context. But since you already have loaded theshiftfrom the database, thisshiftis attached to the context as well. You cannot attach two different instances with the same key to the context. This causes the exception.My code above should work, I try to rewrite it using your service and generic repository. Both must be extended because service and repository don’t seem to be rich enough in functions to perform the task. Especially the generic
Updateis too weak to update an object graph (root object + one or multiple navigation properties).Create a new method in the service:
Findmethod in your generic repository:(Use Ladislav’s
IncludeMultipleextension method ofIQueryabe<T>from here: https://stackoverflow.com/a/5376637/270591)UpdateFlatPropertiesmethod in your generic repository: