1

I am struggling to implement a very basic "InsertOrUpdate()" method on a DbContext. I tried following the advice in this post.

private static bool SaveItem<TEntity>(Object objToSave, TEntity existing = null) where TEntity : class
{
    try
    {
        /////////////////////////////////////////
        // BLOCK A
        if(existing != null)
            db.Set<TEntity>().Attach(existing);
        /////////////////////////////////////////
        db.Entry(objToSave).State = existing!=null ? EntityState.Modified : EntityState.Added;
        db.SaveChanges();
    } catch(Exception e)
    {
        Logger.Exception(e);
        return false;
    }
    return true;
}

An example call to is the following:

SaveItem(item, db.MyInstances.Where(dbItem => dbItem.ID == item.ID).FirstOrDefault());

Some definitions:

class MyInstancesDbContext: DbContext { ... }
private static MyInstancesDbContext db = new MyInstancesDbContext();

As I understand it, in that call the .Where() will cause an attachment of some sort. So I've tried both including the small block of code labeled "A" and removing it. Both of which give me the same kind of error:

System.InvalidOperationException: Attaching an entity of type '...MyInstance' failed because a nother 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 en tities 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 found this popular related answer to this error where the user suggests using AsNoTracking(), but that instead makes me feel like I don't fundamentally understand something or am trying to ignore some error.

I'd greatly appreciate any advice.

Community
  • 1
  • 1
gnychis
  • 7,289
  • 18
  • 75
  • 113
  • Why is `objToSave` of type `Object` and `existing` is a `TEntity`? Shouldn't they be the same? – jjj Jun 07 '15 at 05:51

1 Answers1

0

I think what you're missing is that the DbContext tracks entities, and it doesn't like tracking entities of the same type with the same primary key.

  1. When you call this:

    db.MyInstances.Where(dbItem => dbItem.ID == item.ID).FirstOrDefault()
    

    you've loaded an entity of MyInstance with primary key == item.ID into the context if it exists in the database.

  2. This line is completely unneeded because existing would already be attached -- but that probably doesn't cause the error.

    if(existing != null)
      db.Set<TEntity>().Attach(existing);
    
  3. The problem is probably here:

    db.Entry(objToSave).State = 
    existing != null ? EntityState.Modified : EntityState.Added;
    

    If existing == null, you might be okay, because this line will attach objToSave, but if existing exists, you'll have a problem because you'll be trying to attach objToSave which has the same type and primary key as existing.


Instead, you could try using objToSave to set the values for the attached entity:

db.Entry(existing).CurrentValues.SetValues(objToSave);

So objToSave will not be attached if there is an existing record.

https://msdn.microsoft.com/en-us/data/jj592677.aspx

jjj
  • 4,822
  • 1
  • 16
  • 39