ASP.NET MVC – 附加types“MODELNAME”的实体失败,因为另一个相同types的实体已经具有相同的主键值

简而言之,在POST包装器模型期间抛出exception,并将一个条目的状态更改为“Modified”。 在更改状态之前,状态设置为“Detached”,但调用Attach()会引发相同的错误。 我正在使用EF6。

请在下面find我的代码(型号名称已更改,以便于阅读)

模型

// Wrapper classes public class AViewModel { public A a { get; set; } public List<B> 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.Receipt == null) { return HttpNotFound(); } aViewModel.b = db.Bs.Where(x => x.aID == id.Value).ToList(); aViewModel.Vendor = 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; //THIS IS WHERE THE ERROR IS BEING THROWN db.SaveChanges(); return RedirectToAction("Index"); } return View(aViewModel); } 

如上图所示

 db.Entry(aViewModel.a).State = EntityState.Modified; 

抛出exception:

附加types“A”的实体失败,因为另一个相同types的实体已经具有相同的主键值。 如果图中的任何实体具有冲突的键值,则使用“附加”方法或将实体的状态设置为“未更改”或“已修改”时,可能会发生这种情况。 这可能是因为有些实体是新的,还没有收到数据库生成的关键值。 在这种情况下,使用“添加”方法或“添加”实体状态来跟踪graphics,然后根据情况将非新实体的状态设置为“未更改”或“已修改”。

有人在我的代码中看到任何错误,或者了解在编辑模型期间会在什么情况下抛出这样的错误?

问题解决了!

Attach方法可能有助于某人,但在这种情况下无法帮助,因为文档已被编辑GET控制器函数加载时已被跟踪。 附件会抛出完全相同的错误。

我遇到的问题是由函数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. } 

出于某种原因,我不能使用。 .Find(aID)AsNoTracking()但它并不重要,因为我可以通过更改查询来实现相同的。

希望这会帮助任何有类似问题的人!

有趣的是:

 _dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId); 

或者,如果你仍然不是通用的:

 _dbContext.Set<UserEntity>().AddOrUpdate(entityToBeUpdatedWithId); 

似乎顺利解决了我的问题。

看起来,你正试图修改的实体没有被正确的跟踪,因此不被识别为编辑,而是添加。

不要直接设置状态,请尝试执行以下操作:

 //db.Entry(aViewModel.a).State = EntityState.Modified; db.As.Attach(aViewModel.a); db.SaveChanges(); 

另外,我想警告您,您的代码包含潜在的安全漏洞。 如果您直接在您的视图模型中使用实体,那么您就有可能通过在提交的表单中添加正确命名的字段来修改实体的内容。 例如,如果用户添加名称为“A.FirstName”的input框和包含该字段的实体,则该值将被绑定到视图模型并保存到数据库,即使用户在正常的应用操作中不允许改变该值。

更新:

为了克服前面提到的安全漏洞,你不应该公开你的域模型作为你的视图模型,而应该使用单独的视图模型。 然后,您的操作将会收到viewmodel,您可以使用AutoMapper等映射工具将其映射回域模型。 这可以让你安全的从用户修改敏感数据。

这里是扩展的解释:

http://www.stevefenton.co.uk/Content/Blog/Date/201303/Blog/Why-You-Never-Expose-Your-Domain-Model-As-Your-MVC-Model/

对我来说,本地副本是问题的根源。 这解决了它

 var local = context.Set<Contact>().Local.FirstOrDefault(c => c.ContactId == contact.ContactId); if (local != null) { context.Entry(local).State = EntityState.Detached; } 

我的情况是,我没有从我的MVC应用程序直接访问EF上下文。

所以,如果你正在使用某种types的实体持久性存储库,它可能适用于简单地分离明确加载的实体,然后将绑定的EntityState设置为修改。

示例(摘要)代码:

MVC

 public ActionResult(A a) { A aa = repo.Find(...); // some logic repo.Detach(aa); repo.Update(a); } 

知识库

 void Update(A a) { context.Entry(a).EntityState = EntityState.Modified; context.SaveChanges(); } void Detach(A a) { context.Entry(a).EntityState = EntityState.Detached; } 

我想我会分享一下我的经验,即使我觉得有点傻,没有及早意识到。

我正在使用存储库模式与注入我的控制器的回购实例。 具体存储库实例化我的ModelContext(DbContext),它持续存储库的生命周期,这是IDisposable和控制器处置。

我的问题是,我的实体上有一个修改后的邮票和行版本,所以我首先得到它们,以便与入站邮件头进行比较。 当然,这加载和追踪随后被更新的实体。

解决方法是简单地将存储库从构造函数中的一个上下文更改为具有以下方法:

  private DbContext GetDbContext() { return this.GetDbContext(false); } protected virtual DbContext GetDbContext(bool canUseCachedContext) { if (_dbContext != null) { if (canUseCachedContext) { return _dbContext; } else { _dbContext.Dispose(); } } _dbContext = new ModelContext(); return _dbContext; } #region IDisposable Members public void Dispose() { this.Dispose(true); } protected virtual void Dispose(bool isDisposing) { if (!_isDisposed) { if (isDisposing) { // Clear down managed resources. if (_dbContext != null) _dbContext.Dispose(); } _isDisposed = true; } } #endregion 

这允许存储库方法在每次使用时通过调用GetDbContext来重新创build它们的上下文实例,或者通过指定true来使用以前的实例。

我只是添加了这个答案,因为问题是基于更复杂的数据模式解释的,我发现在这里很难理解。

我创build了一个很简单的应用程序 编辑POST操作内发生此错误。 该操作接受ViewModel作为input参数。 使用ViewModel的原因是在logging保存之前进行一些计算。

一旦动作通过if(ModelState.IsValid)这样的validation,我的错误就是将ViewModel中的值投影到一个全新的实体实例中。 我想我必须创build一个新的实例来存储更新的数据,然后保存这样的实例。

我后来意识到,我必须从数据库中读取logging:

 Student student = db.Students.Find(s => s.StudentID == ViewModel.StudentID); 

并更新了这个对象。 现在一切正常。

尝试这个:

 var local = yourDbContext.Set<YourModel>() .Local .FirstOrDefault(f => f.Id == yourModel.Id); if (local != null) { yourDbContext.Entry(local).State = EntityState.Detached; } yourDbContext.Entry(applicationModel).State = EntityState.Modified; 

ViewModelEntityModel映射(通过使用AutoMapper等)和试图包含context.Entry().Statecontext.SaveChanges()这样一个使用如下所示的块将会解决这个问题。 请记住context.SaveChanges()方法使用了两次,而不是在if-block之后使用,因为它也必须在使用块中。

 public void Save(YourEntity entity) { if (entity.Id == 0) { context.YourEntity.Add(entity); context.SaveChanges(); } else { using (var context = new YourDbContext()) { context.Entry(entity).State = EntityState.Modified; context.SaveChanges(); //Must be in using block } } } 

希望这可以帮助…

在这里,我在类似的情况下做了什么。

那个sitatuation意味着同一个实体已经在上下文中存在了,所以下面可以帮忙

首先从ChangeTracker中检查实体是否在上下文中

 var trackedEntries=GetContext().ChangeTracker.Entries<YourEntityType>().ToList(); var isAlreadyTracked = trackedEntries.Any(trackedItem => trackedItem.Entity.Id ==myEntityToSave.Id); 

如果存在

  if (isAlreadyTracked) { myEntityToSave= trackedEntries.First(trackedItem => trackedItem.Entity.Id == myEntityToSave.Id).Entity; } else { //Attach or Modify depending on your needs } 

类似于卢普Puplett说,这个问题可能是由不妥善处理或创build你的上下文造成的。

就我而言,我有一个接受ContextService上下文的类:

 public class ContextService : IDisposable { private Context _context; public void Dispose() { _context.Dispose(); } public ContextService(Context context) { _context = context; } //... do stuff with the context 

我的上下文服务具有使用实例化的实体对象来更新实体的function:

  public void UpdateEntity(MyEntity myEntity, ICollection<int> ids) { var item = _context.Entry(myEntity); item.State = EntityState.Modified; item.Collection(x => x.RelatedEntities).Load(); myEntity.RelatedEntities.Clear(); foreach (var id in ids) { myEntity.RelatedEntities.Add(_context.RelatedEntities.Find(id)); } _context.SaveChanges(); } 

所有这一切都很好,我的控制器,我初始化服务是问题。 我的控制器最初看起来像这样:

  private static NotificationService _service = new NotificationService(new NotificationContext()); public void Dispose() { } 

我改变了这个,错误消失了:

  private static NotificationService _service; public TemplateController() { _service = new NotificationService(new NotificationContext()); } public void Dispose() { _service.Dispose(); } 
 using (var dbE = new HomologacionContext()) { Empresa empNombre = dbE.Empresas.Where(e => e.Nombre.ToLower() == empresa.Nombre.ToLower()).FirstOrDefault(); if (empNombre != null) { if (empNombre.NitEmpresa != empresa.NitEmpresa) { ViewBag.Existe = "Existe una empresa con ese nombre"; return View(empresa); } } } db.Entry(empresa).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index");` 

可能,一个实体从数据库,然后尝试保存到数据库几次相同的数据库上下文。 改用EntityState.Added

尝试:

 db.Entry(aViewModel.a).State = EntityState.Added; 

我有这个问题与本地变种,我只是像这样分离它:

 if (ModelState.IsValid) { var old = db.Channel.Find(channel.Id); if (Request.Files.Count > 0) { HttpPostedFileBase objFiles = Request.Files[0]; using (var binaryReader = new BinaryReader(objFiles.InputStream)) { channel.GateImage = binaryReader.ReadBytes(objFiles.ContentLength); } } else channel.GateImage = old.GateImage; var cat = db.Category.Find(CatID); if (cat != null) channel.Category = cat; db.Entry(old).State = EntityState.Detached; // just added this line db.Entry(channel).State = EntityState.Modified; await db.SaveChangesAsync(); return RedirectToAction("Index"); } return View(channel); 

带有相同Key的已加载对象的问题原因,所以首先我们将分离该对象并进行更新以避免具有相同Key的两个对象之间的冲突

我pipe理通过更新状态来解决问题。 当你触发查找或任何其他查询操作相同的logging状态已更新与修改,所以我们需要设置状态为独立,那么你可以触发你的更新更改

  ActivityEntity activity = new ActivityEntity(); activity.name="vv"; activity.ID = 22 ; //sample id var savedActivity = context.Activities.Find(22); if (savedActivity!=null) { context.Entry(savedActivity).State = EntityState.Detached; context.SaveChanges(); activity.age= savedActivity.age; activity.marks= savedActivity.marks; context.Entry(activity).State = EntityState.Modified; context.SaveChanges(); return activity.ID; } 

我在问题中引用了相同的exception消息,在EF6下使用断开的上下文。

在消息的指令之后,我从字面上将实体的状态设置了两次:

 if (entityWithState.EntityState == EntityState.Modified) { context.Entry(item).State = EntityState.Added; context.Entry(item).State = EntityState.Modified; } 

解决了我的问题,很违反直觉,所以也许会帮助别人。