实体框架更新导航实体
我正在开发一个ASP.Net MVC 3 Web应用程序使用实体框架4.1,但是,我有更新实体的导航实体的问题。实体框架更新导航实体
我有一个实体,称为移
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; }
}
在我的换档控制器,我有两个HttpPost方法来创建和编辑的转变。
[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");
}
}
的CreateShift法正常工作,即插入一条记录到数据库中的一个新的转变,也为另一个表内的移位位置上的新纪录。但是,EditShift方法仅更新Shift记录,但它不会更新Shift位置记录,即使我已分配了shiftID和新的locationID。
关于如何解决这个问题的任何信息将不胜感激。
谢谢。
编辑
我ShiftLocation类是如下
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; }
}
的ShiftService类中的UpdateShift方法如下
public void UpdateShift(Shift item)
{
_UoW.Shifts.Update(item);
_UoW.Commit();
}
这然后调用Update方法我通用库
public void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
第二编辑
从Slauma的回答继我编辑EditShift POST操作以下
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");
}
我现在的问题是,当我在通用存储库更新方法被调用,以下发生错误
An object with the same key already exists in the ObjectStateManager.
The ObjectStateManager cannot track multiple objects with the same key
现在,我现在为什么会发生这种情况,因为在我的EditPost操作中,我检索Shift的一个实例,然后在通用库中的更新方法中,我试图附加相同的Shift。
我现在只是很困惑。 My Generic Repositro看起来不错,因为我从本文顶部提到的Microsoft MVC教程中复制了它。
再次有关如何获得这个简单的更新工作的任何帮助将不胜感激。
关于你的评论...
当我到了“EditShift”后的行动,我想更新了“移” 与从通过检索在 “ViewModelShift新的细节模型'。这个ViewModel也将包含 'locationID'只有'ShiftLocation',但是,这个ShiftLocation 已经存在于数据库中,我只是想用新的 locationID更新它。
...我想试试这个(忽略你的资料库结构):
[HttpPost]
public ActionResult EditShift(ViewModelShift model)
{
if (ModelState.IsValid)
{
Shift shift = context.Shifts
.Include(s => s.Locations)
.Single(s => s.shiftID == model.shiftID);
context.Entry(shift).CurrentValues.SetValues(model);
ShiftLocation location = shift.Locations.Single();
// because you expect exactly one location
location.locationID = model.locationID;
context.SaveChanges();
return RedirectToAction("Index");
}
//...
}
设置shift
到Modified
的状态不工作,因为它不影响任何相关的实体。该位置仍然处于Unchanged
状态。您可能也将位置的状态设置为Modified
,但它看起来像您的ViewModel中没有主键属性shiftLocationID
。所以,找到正确位置更新的唯一方法是通过shift的导航属性(在上例中= Include
)加载它。
编辑
在你的第二编辑代码不起作用,因为AutoMapper创建的shift
一个新的实例,以后你重视这种转变的背景下。但是由于您已经从数据库中加载了shift
,因此该shift
也会附加到上下文中。您无法将两个不同的实例附加到上下文中。这导致异常。
我上面的代码应该可以工作,我尝试使用你的服务和通用存储库来重写它。两者都必须扩展,因为服务和存储库在执行任务的功能上似乎不够丰富。尤其是通用的Update
太弱,无法更新对象图(根对象+一个或多个导航属性)。
[HttpPost]
public ActionResult EditShift(ViewModelShift model)
{
if (ModelState.IsValid)
{
Shift shift = Mapper.Map<ViewModelShift, Shift>(model);
_shiftService.UpdateShiftAndLocation(shift, model.LocationID);
return RedirectToAction("Index");
}
//...
}
创建新的服务方法:
public IQueryable<T> Find(Expression<Func<T, bool>> predicate,
params Expression<Func<T, object>>[] includes)
{
return dbSet.IncludeMultiple(includes)
.Where(predicate);
}
(从这里的IQueryabe<T>
使用拉吉斯拉夫的IncludeMultiple
扩展方法:https://*.com/a/5376637/270591)
public void UpdateShiftAndLocation(Shift detachedShift, int locationID)
{
Shift attachedShift = _UoW.Shifts.Find(
s => s.ShiftID == detachedShift.ShiftID, s => s.Locations);
_UoW.Shifts.UpdateFlatProperties(attachedShift, detachedShift);
ShiftLocation location = attachedShift.Locations.Single();
// MUST be exactly one location for the Shift in the DB
// ToDo: Catch the case somehow, if there is no or more than one location
location.LocationID = locationID;
_UoW.Commit();
}
Find
方法
UpdateFlatProperties
方法在通用存储库:
// "Flat" means: Scalar and Complex properties, but not Navigation properties
public void UpdateFlatProperties(T attachedEntity, T detachedEntity)
{
context.Entry(attachedEntity).CurrentValues.SetValues(detachedEntity);
}
请参阅我上面的第二编辑。还有什么想法?谢谢。 – tgriffiths 2012-04-05 11:50:28
@tgriffiths:你可以仔细检查第二次编辑中的代码吗?不知何故,它没有任何意义:您将'shiftLocation'添加到'shift.Locations'集合中,但之前从完全相同的集合中检索到'shiftLocation'。另外,你实例化一个新的'location',但是对这个实例不做任何事情。 – Slauma 2012-04-05 12:01:15
我删除了实例的位置,因为它没有做任何事情。我将shiftLocation添加到shift位置,因为我的AutoMapper不会为我执行此操作。一旦Mapper.Map代码运行,shift.Locations = 0,因此我将shiftLocation添加到新更新的'locationID'中以便shift.Locations。但问题仍然存在,即在进行更新时,ObjectStateManager中已存在密钥。 – tgriffiths 2012-04-05 13:14:13
您可以显示'ShiftLocation'类和'_shiftService.UpdateShift(移)'里面的代码? – Slauma 2012-04-03 18:11:46
@Slauma - 请参阅我的编辑。谢谢。 – tgriffiths 2012-04-03 19:56:36
你想要达到什么目的?想象一下,“shift”存储在数据库中,并且在DB中已经有5个“ShiftLocations”分配给它。现在你到达'EditShift'后期操作。在你的代码中,你创建一个'ShiftLocation'对象并将它添加到'shift.Locations'集合中。您现在需要:1)将这个新的位置添加到已有的位置,或者2)您想删除现有的5个位置并将其替换为新的位置,或者3)您是否希望找到位置为其中一个(通过Id?),然后用新值更新它? – Slauma 2012-04-03 20:49:24