更新EF 6中的现有数据会引发异常 - “...相同类型的实体已具有相同的主键值。”

时间:2015-06-07 07:08:42

标签: c# entity-framework entity-framework-6

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

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

它还继承自名为User

的基础

保存方法如下,_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; // <- Exception raised here
                _dbContext.Employees.Attach(employee);

            }

            return _dbContext.SaveChanges() > 0;

        }

我一直收到错误:

  

附加类型的实体失败,因为另一个实体是相同的   type已经具有相同的主键值。这可能发生在   使用'Attach'方法或将实体的状态设置为   如果图中的任何实体具有“未更改”或“已修改”   冲突的关键值。这可能是因为一些实体是新的和   尚未收到数据库生成的键值。在这种情况下使用   “添加”方法或“已添加”实体状态可跟踪图表和   然后将非新实体的状态设置为“未更改”或“已修改”为   合适的。

我尝试了以下内容:

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

    现在没有一个对我有用。

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

4 个答案:

答案 0 :(得分:21)

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语句,因为它只会更新已更改的属性。

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

答案 1 :(得分:5)

这对我自己有用

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;
}

答案 2 :(得分:3)

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

GenericRepository包含一个Update(TEntity)方法,该方法尝试Attach,然后设置Entry.State = Modified。最近投票的回答&#39;如果您要坚持使用uow / repo模式,上面没有解决这个问题。

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

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

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

以下是通用repo的更新实现:

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

这是我的PUT方法(我使用带角度的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<ProductEntity>(product);
        unitOfWork.ProductRepository.Update(toSave);
        unitOfWork.Save();
        var p = _mapper.Map<Product>(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,然后进行更新。

答案 3 :(得分:2)

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

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