ASP.NET MVC - 附加一个类型为 'MODELNAME' 的实体失败,因为另一个具有相同主键值的同类型实体已存在。
ASP.NET MVC - 附加一个类型为 'MODELNAME' 的实体失败,因为另一个具有相同主键值的同类型实体已存在。
简而言之,在POST包装模型并将一个条目的状态更改为“Modified”时,会抛出异常。在更改状态之前,状态被设置为“Detached”,但调用Attach()时会抛出相同的错误。我正在使用EF6。
以下是我的代码(模型名称已更改以便阅读):
模型:
// 包装类 public class AViewModel { public A a { get; set; } public List b { get; set; } public C c { get; set; } }
控制器:
public ActionResult Edit(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } if (!canUserAccessA(id.Value)) { return new HttpStatusCodeResult(HttpStatusCode.Forbidden); } var aViewModel = new AViewModel(); aViewModel.a = db.As.Find(id); if (aViewModel.a == null) { return HttpNotFound(); } aViewModel.b = db.Bs.Where(x => x.aID == id.Value).ToList(); aViewModel.c = db.Cs.Where(x => x.cID == aViewModel.a.cID).FirstOrDefault(); return View(aViewModel); } [HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit(AViewModel aViewModel) { if (!canUserAccessA(aViewModel.a.aID) || aViewModel.a.UserID != WebSecurity.GetUserId(User.Identity.Name)) { return new HttpStatusCodeResult(HttpStatusCode.Forbidden); } if (ModelState.IsValid) { db.Entry(aViewModel.a).State = EntityState.Modified; // 此处抛出错误 db.SaveChanges(); return RedirectToAction("Index"); } return View(aViewModel); }
如上所示的代码行 `db.Entry(aViewModel.a).State = EntityState.Modified;` 抛出异常:
“因为存在具有相同主键值的相同类型的另一个实体,所以无法附加类型为 'A' 的实体。如果图中的任何实体具有冲突的主键值,当使用 'Attach' 方法或将实体的状态设置为 'Unchanged' 或 'Modified' 时,可能会发生这种情况。这可能是因为某些实体是新的,尚未收到数据库生成的键值。在这种情况下,使用 'Add' 方法或 'Added' 实体状态来跟踪图,然后根据需要将非新实体的状态设置为 'Unchanged' 或 'Modified'。”
请问是否有人发现我的代码有什么问题,或者了解在编辑模型时会在什么情况下抛出此错误?
ASP.NET MVC - Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value
在ASP.NET MVC中,当尝试修改一个实体时,可能会出现上述错误。这通常是因为实体没有被正确跟踪,导致被认为是新添加的实体,而不是被编辑过的实体。
解决方法是使用以下代码替代直接设置状态的方式:
//db.Entry(aViewModel.a).State = EntityState.Modified; db.As.Attach(aViewModel.a); db.SaveChanges();
此外,需要注意的是,代码中存在潜在的安全漏洞。如果在视图模型中直接使用实体,则有可能通过在提交的表单中添加正确命名的字段来修改实体的内容。例如,如果用户添加了一个名为"A.FirstName"的输入框,并且实体中包含该字段,则该值将绑定到视图模型并保存到数据库中,即使在应用程序的正常操作中用户不允许更改该值。
为了解决之前提到的安全漏洞,应该避免将领域模型直接暴露为视图模型,而应该使用单独的视图模型。然后,您的操作将接收视图模型,并可以使用AutoMapper等映射工具将其映射回领域模型。这样可以确保用户无法修改敏感数据。
关于如何防止安全漏洞的详细说明,请参考以下链接:
最后,还需要注意的是,使用Attach方法可能会出现与问题描述中相同的错误。这是因为canUserAccessA()函数加载了实体,而CodeCaster在上面已经注意到了这一点。
问题的原因是在加载Edit GET控制器函数中的文档时,在更新对象a的状态之前,函数canUserAccessA()加载了实体A。这会破坏被跟踪的实体,并将a对象的状态更改为Detached。
解决方法是修改canUserAccessA()函数,使加载的对象不被跟踪。在查询上下文时应调用AsNoTracking()方法。
代码示例:
// User -> Receipt validation private bool canUserAccessA(int aID) { int userID = WebSecurity.GetUserId(User.Identity.Name); int aFound = db.Model.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count(); return (aFound > 0); //if aFound > 0, then return true, else return false. }
对于某种原因,我无法在AsNoTracking()中使用.Find(aID),但这并不重要,因为我可以通过更改查询来实现相同的效果。
希望这对于遇到类似问题的人有所帮助!稍微整洁且性能更好的代码示例:if (db.As.AsNoTracking().Any(x => x.aID == aID && x.UserID==userID))
注意:您需要using System.Data.Entity;来使用AsNoTracking()。
在我的情况下,仅更新除实体ID以外的字段也可以正常工作:
var entity = context.Find(entity_id); entity.someProperty = newValue; context.Entry(entity).Property(x => x.someProperty).IsModified = true; context.SaveChanges();
非常有帮助。我在FirstOrDefault()之前添加了.AsNoTracking(),它起作用了。
在ASP.NET MVC中,当尝试将一个具有相同主键值的实体附加到上下文中时,会出现"Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value"的错误。这个错误的出现通常是因为在上下文中已经存在一个具有相同主键值的实体。
解决这个问题的方法是使用"AddOrUpdate"方法来附加实体到上下文中。例如,可以使用以下代码来解决这个问题:
_dbContext.Set().AddOrUpdate(entityToBeUpdatedWithId);
如果不是泛型,则可以使用以下代码来解决:
_dbContext.Set().AddOrUpdate(entityToBeUpdatedWithId);
这个方法可以很好地解决在需要在断开连接的应用程序中更新具有自定义连接表的多对多关系的记录的场景。即使从数据库中获取了实体,也可以避免引用错误。
需要注意的是,"AddOrUpdate"是"System.Data.Entity.Migrations"命名空间中的扩展方法。
需要注意的是,这个方法仅适用于实体状态为"Added"或"Modified"的情况。对于实体状态为"Deleted"的情况,需要使用另一种方法。目前还没有提供与"AddOrUpdate"类似的方法来处理"EntityState=Deleted"的情况。
以上是关于"Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value"问题的原因及解决方法的整理。这种方法在许多情况下都能解决该问题,但仍然有些人对为什么设置状态为"Modified"不起作用感到好奇。