9

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 composite properties associated with it like Addreess(collection), Department

It is also inherited from a base called User

The save method is as follows, with _dbContext being the DbConext implementation

        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;

        }

I keep getting the error:

Attaching an entity of type failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.

I have tried the following:

  1. Attaching before setting to EntityState.Modified
  2. Adding AsNoTracking() on querying if the object exists(No exception but DB is not updated) - https://stackoverflow.com/a/23228001/919426
  3. Saving using the base entity _dbContext.Users instead of the Employee entity - https://stackoverflow.com/a/25575634/919426

None of which is working for me now.

What could I have gotten wrong for some of those solutions not to work in my situation?

Community
  • 1
  • 1
tinonetic
  • 7,751
  • 11
  • 54
  • 79
  • use `var entity = _dbContext.Employees.FirstOrDefault(c => c.Id == employee.Id);` – Hamid Pourjam Jun 07 '15 at 07:10
  • What's the lifespan of `dbContext`? I bet you don't create a new context often enough. – Gert Arnold Jun 07 '15 at 18:10
  • Please have a look at my answer on [ASP.NET MVC - Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value](http://stackoverflow.com/questions/23201907/asp-net-mvc-attaching-an-entity-of-type-modelname-failed-because-another-ent/39557606#39557606). – Murat Yıldız Sep 18 '16 at 12:27

4 Answers4

24

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.

ESG
  • 8,988
  • 3
  • 35
  • 52
  • I did mention I am not using Automapper. I do have navigatin properties (in composite classes ike `Address` & `Department`. Are you saying I need Automapper then?. Your code did not result in an exception but the DB was not updated – tinonetic Jun 07 '15 at 15:02
  • Are you modifying the properties of the object, or something else? If you are modifying a whole object graph, you'll have to go through that graph and mark all the items as modified. – ESG Jun 07 '15 at 15:22
  • I have an `Employee` record that I would like to update using Entiy Framework (new to it). As far as I have reserched, the method I used should work. I dont know where the object graph is involved in the process? Please explain the alternative, given your last comment. I have an object `Employee` with navigation properties. I want to update that ONLY THAT`Employee` record...How is that done? and what may be causing the error I got using the method I am using? – tinonetic Jun 07 '15 at 15:35
  • If you're only update the employee object, this method should work. I'd look at what SQL statement is being passed to the DB in that case. Your original method doesn't work because you cannot have the same entity twice in the same DbContext (in this case, the original object and the one you are trying to update) – ESG Jun 07 '15 at 15:47
  • Ok. Will have to review code and profile the DB then after restarting both DB Server and client PC(just in case), will test all suggested methods – tinonetic Jun 07 '15 at 16:56
6

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;
}
bRySaGeeK
  • 151
  • 2
  • 2
3

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.

PixelSyndicate
  • 331
  • 3
  • 5
2

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

db.Entry(entity).State = EntityState.Detached;
Karan Bhandari
  • 370
  • 3
  • 12