Updating existing data in EF 6 throws exception - “…entity of the same type already has the same primary key value.”

前端 未结 4 1704
眼角桃花
眼角桃花 2020-12-29 13:39

I am trying to update a record using Entity Framework 6, code-first, no fluent mapping or a tool like Automapper.

The entity(Employee) has other composi

相关标签:
4条回答
  • 2020-12-29 14:09

    This worked for myself

    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;
    }
    
    0 讨论(0)
  • 2020-12-29 14:15

    I've encountered the same thing when using a repository and unit of work pattern (as documented in the mvc4 with ef5 tutorial).

    The GenericRepository contains an Update(TEntity) method that attempts to Attach then set the Entry.State = Modified. The up-voted 'answer' above doesn't resolve this if you are going to stick to the uow / repo pattern.

    I did attempt to use the detach process prior to the attach, but it still failed for the same reason as indicated in the initial question.

    The reason for this, it turns out, is that I was checking to see if a record existed, then using automapper to generate an entity object from my dto prior to calling update().

    By checking for the existance of that record, i put the entity object in scope, and wasn't able to detach it (which is also the reason the initial questioner wasn't able to detach)... Tt tracked the record and didn't allow any changes after I automapper'ed the dto into an entity and then attempted to update.

    Here's the generic repo's implementation of update:

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

    This is my PUT method (i'm using WebApi with Angular)

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

    As you can see, i've commented out my check to see if the record exists. I guess i'll see how it works if I attempt to update a record that no longer exists, as i no longer have a NotFound() return opportunity.

    So to answer the initial question, i'd say don't look for entity==null before making the attempt, or come up with another methodology. maybe in my case, i could dispose of my UnitOfWork after discovery of the object and then do my update.

    0 讨论(0)
  • 2020-12-29 14:18

    You need to detach to avoid duplicate primary key exception whist invoking SaveChanges

    db.Entry(entity).State = EntityState.Detached;
    
    0 讨论(0)
  • 2020-12-29 14:19

    EF already includes a way to map properties without resorting to Automapper, assuming you do not have navigation properties to update:

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

    This usually generates a better SQL statement since it will only update the properties that have changed.

    If you still want to use the original method, you'll get rid of entity from the context, either using AsNoTracking (not sure why it didn't update in your case, it should have no effect, so the problem might be something else) or as modifying your query to prevent it from materializing the entity in the first place, using something like bool exists = dbContext.Employees.Any(c => c.Id == employee.Id) for example.

    0 讨论(0)
提交回复
热议问题