更新EF 6中的现有数据会引发exception – “…相同类型的实体已具有相同的主键值”。

我正在尝试使用Entity Framework 6更新记录,代码优先,没有流畅的映射或像Automapper这样的工具。

实体( Employee )具有与其相关联的其他复合属性,如Addreess (集合), Department

它也从一个名为User的基础inheritance而来

save方法如下, _dbContextDbConext实现

  public bool UpdateEmployee(Employee employee) { var entity = _dbContext.Employees.Where(c => c.Id == employee.Id).AsQueryable().FirstOrDefault(); if (entity == null) { _dbContext.Employees.Add(employee); } else { _dbContext.Entry(employee).State = EntityState.Modified; //  0; } 

我一直收到错误:

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

我尝试过以下方法:

  1. 在设置为EntityState.Modified之前附加
  2. 在查询对象是否存在时添加AsNoTracking() (没有例外,但DB没有更新) – https://stackoverflow.com/a/23228001/919426
  3. 使用基本实体_dbContext.Users而不是Employee实体进行保存 – https://stackoverflow.com/a/25575634/919426

现在这些都不适合我。

对于那些不能在我的情况下工作的解决方案,我有什么问题?

假设您没有要更新的导航属性 ,EF已经包含了一种映射属性而无需借助Automapper的方法:

 public bool UpdateEmployee(Employee employee) { var entity = _dbContext.Employees.Where(c => c.Id == employee.Id).AsQueryable().FirstOrDefault(); if (entity == null) { _dbContext.Employees.Add(employee); } else { _dbContext.Entry(entity).CurrentValues.SetValues(employee); } return _dbContext.SaveChanges() > 0; } 

这通常会生成更好的SQL语句,因为它只会更新已更改的属性。

如果您仍然想使用原始方法,您将从上下文中删除entity ,使用AsNoTracking(不确定为什么它在您的情况下没有更新,它应该没有效果,所以问题可能是其他的或者作为修改查询以防止它首先实现实体,例如使用bool exists = dbContext.Employees.Any(c => c.Id == employee.Id)

这对我自己有用

 var aExists = _db.Model.Find(newOrOldOne.id); if(aExists==null) { _db.Model.Add(newOrOldOne); } else { _db.Entry(aExists).State = EntityState.Detached; _db.Entry(newOrOldOne).State = EntityState.Modified; } 

我在使用存储库和工作单元模式时遇到了同样的问题(如mvc4和ef5教程中所述 )。

GenericRepository包含一个Update(TEntity)方法,尝试Attach,然后设置Entry.State = Modified。 如果您要坚持使用uow / repo模式,上面提到的’回答’并不能解决这个问题。

我确实尝试在附加之前使用分离过程,但它仍然失败的原因与初始问题中指出的相同。

事实certificate,原因是我正在检查是否存在记录,然后在调用update()之前使用automapper从我的dto生成实体对象。

通过检查该记录的存在,我将实体对象放在范围内,并且无法将其分离(这也是初始提问者无法分离的原因)… Tt跟踪记录并且没有在我自动将dto导入实体然后尝试更新之后,不允许任何更改。

这是通用repo的更新实现:

 public virtual void Update(TEntity entityToUpdate) { dbSet.Attach(entityToUpdate); context.Entry(entityToUpdate).State = EntityState.Modified; } 

这是我的PUT方法(我正在使用Angular的WebApi)

 [HttpPut] public IHttpActionResult Put(int id, Product product) { IHttpActionResult ret; try { // remove pre-check because it locks the record // var e = unitOfWork.ProductRepository.GetByID(id); // if (e != null) { var toSave = _mapper.Map(product); unitOfWork.ProductRepository.Update(toSave); unitOfWork.Save(); var p = _mapper.Map(toSave); ret = Ok(p); // } // else // ret = NotFound(); } catch (DbEntityValidationException ex) { ret = BadRequest(ValidationErrorsToMessages(ex)); } catch (Exception ex) { ret = InternalServerError(ex); } return ret; } 

正如你所看到的,我已经注释掉了我的支票以查看记录是否存在。 我想如果我尝试更新不再存在的记录,我会看到它是如何工作的,因为我不再有NotFound()返回机会。

所以回答最初的问题,我会说在尝试之前不要寻找实体== null,或者想出另一种方法。 也许在我的情况下,我可以在发现对象后处理我的UnitOfWork,然后进行更新。

您需要分离以避免重复主键exception,并调用SaveChanges

 db.Entry(entity).State = EntityState.Detached;