6

I'm using Entity Framework 6 and ObjectCache to cache some entities that don't change often. But I faced an error when trying to save entities that are cached because they were retrieved from a different context. Before saving, I verified and the state of the object was detached but couldn't get rid of that error until I did this:

public void Save(Product obj)
{
    var objInDb = dbContext.Products.Find(obj.Id);

    if (objInDb == null)
    {
        dbContext.Products.Add(obj);
        dbContext.SaveChanges();
    }
    else
    {
        dbContext.Entry(objInDb).CurrentValues.SetValues(obj);
        dbContext.Entry(objInDb).State = EntityState.Modified;
        dbContext.SaveChanges();
    }
}

Original error that was fixed after implemented the described solution: Attaching an entity of type 'C' 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.

The original code was:

if (obj.Id == 0)
{
    context.Products.Add(obj);
}
else
{
    context.Entry(obj).State = EntityState.Modified;
}

context.SaveChanges();

Is there a better way to handle this? I really don't like to bypass my cache and hit the DB to get the object because it looks unnecessary to me.

Francisco Goldenstein
  • 13,299
  • 7
  • 58
  • 74
  • What was the original code that didn't work then? – DavidG Jul 24 '17 at 16:11
  • I have just added the original code in my question. – Francisco Goldenstein Jul 24 '17 at 16:23
  • Do you have `Configuration.ProxyCreationEnabled` enabled in your context? My guess is that you are storing the proxy in your cache rather than the actual entity type. – DavidG Jul 24 '17 at 16:27
  • It is enabled but I also tried deactivating it before populating the cache and then reactivating it but the result was the same. – Francisco Goldenstein Jul 24 '17 at 17:39
  • You need to deactivate before retrieving the entity from the context. You may also want to disable change tracking. – DavidG Jul 24 '17 at 17:52
  • I did that. Deactivated proxy creation, retrieved objects, reactivated. ChangeTracking is already disabled when populating the cache. – Francisco Goldenstein Jul 24 '17 at 18:00
  • *because they were retrieved from a different context* The error message clearly indicates that this is not the issue. Looks like you have another object instance with the same PK tracked by your target `context`. It depends on many factors: lifetime of your target `context`, the queries you have executed using it, navigation properties referencing your entity etc. The "fixed" snippet is in general the correct way to update disconnected entity (there is no need to set `State` to `Modified`). Note that `Find` is using the local context cache, hence does not necessarily hit the database. – Ivan Stoev Jul 27 '17 at 13:39
  • That's why I pointed out I was using ObjectCache. The object is the same I initially retrieved from the DB so yes, it is tracked by another DbContext that is out of scope. I checked the collection and there are no object with the same PK. I agree with you that Find could hit the DB but it's not guaranteed. – Francisco Goldenstein Jul 27 '17 at 14:06
  • I understand that the entity has been retrieved from another context. What I'm saying is that if it was tracked in the original context, the exception would be different (*An entity object cannot be referenced by multiple instances of IEntityChangeTracker.*). So definitely there is another object with the same PK in the context used for saving. Did you check the `context.Products.Local` or `db.ChangeTracker.Entries()`? I'm sure EF is not lying :) – Ivan Stoev Jul 28 '17 at 11:18
  • Your code with cache works fine also with proxied entities. You can see a sample with exactly your code here https://github.com/bubibubi/JetEntityFrameworkProvider/tree/master/JetEntityFrameworkProvider.Test/Model65_InMemoryObjects (look at Test.cs). There is another issue probably related to the model (entities related with Product). – bubi Jul 28 '17 at 14:50

1 Answers1

0

I think the only way of not "hitting" de db would be something like the following. Please keep in mind that, based on your first exception, you are already "hitting" the db for this entity somewhere else.

if (obj.Id == 0)
{
    context.Products.Add(obj);
}
else
{
    var localProduct = context.Products.Local.FirstOrDefault(x => x.Id == obj.Id);
    if (localProduct == null)
    {
        context.Entry(obj).State = EntityState.Modified;
    }
    else
    {
        context.Entry(localProduct).CurrentValues.SetValues(obj);
    }
}
context.SaveChanges();
Henrique Campos
  • 491
  • 4
  • 8