I received this exception while debugging in VS 2012
An object with the same key already exists in the ObjectStateManager.
The ObjectStateManager cannot track multiple objects with the same
key.
//_dbSet declaration:
private readonly IDbSet<T> _dbSet;
//Method parameter
public virtual void Update(T entity)
//method fragment
public virtual void Update(T entity)
{
if (Entities == null) return;
var entry = Entities.Entry(entity);
switch (entry.State)
{
case EntityState.Modified:
var currentValues = entry.CurrentValues.Clone();
entry.Reload();
switch (entry.State)
{
case EntityState.Detached:
Entities.Entry(entry).State = EntityState.Modified;
break;
default:
entry.Reload();
entry.CurrentValues.SetValues(currentValues);
break;
}
break;
case EntityState.Detached:
_dbSet.Attach(entity); /*Here is the thing*/
entry.CurrentValues.SetValues(entity);
break;
}
Entities.Commit();
}
I have spent almost a week trying to solve optimistic concurrency with these patterns:
DbFactory, Unit of Work, DI, Generic Repository, without getting results.
I believe the exception message is clear enough:
An entity with the same key (that is, an entity that maps to the same database record) has already been loaded and attached in the target DbSet.
What you decide to do in that case is entirely up to you:
You can get the already loaded equivalent entry and modify the values of the attached equivalent to match the entity instance (or anything more complicated);
You can detach the equivalent loaded entry and attach your parameter entity instead;
You can just skip over it;
The code smells bad;
The first switch statement uses the entry.State and checking whether the entity is in modified state or in detached state; then in the modified case , a new switch statement is created to check whether the entity is detached or in other state.
The exception states nothing about the optimistic concurrency; instead it states that you are trying to attach an entity to the object context and an entity with the same key already exists in the context.
I would like to thank all of whom dedicated a bit of their precious time to my headake.
Here is a new post clearifying my final solution for concurrent Update method in Generic Repository pattern. Nothing was lost thanks to this article:
Seeking Entity's Key by Attribute Along
That was very useful: RoccoC5
private object GetKeyValue(T entity)
{
var key =
typeof(T).GetProperties().FirstOrDefault(
p => p.GetCustomAttributes(typeof(KeyAttribute), true).Length != 0);
return (key != null) ? key.GetValue(entity, null) : null;
}
public virtual void Update(T entity)
{
if (Entities == null) return;
var key = GetKeyValue(entity);
var originalEntity = _dbSet.Find(key);
Entities.Entry(originalEntity).CurrentValues.SetValues(entity);
Entities.Commit();
}
Related
I implemented similar solution on how we can modify created and updated date upon saving data through EF Core as what is suggested here Populate Created and LastModified automagically in EF Core.
void OnEntityStateChanged(object sender, EntityStateChangedEventArgs e)
{
if (e.NewState == EntityState.Modified && e.Entry.Entity is IHasCreationLastModified entity)
entity.LastModified = DateTime.Now;
}
At first I thought this will be triggered only when SaveChanges() is called. But apparently it is also called on Entry()
// Get entity
var student = _dbContext.Students.Find(studentId);
// Modify student object
student.Name = "New student name";
// Called Entry(), trigger ChangeTracker.StateChanged
var entry = _dbContext.Entry(student);
// Doesn't trigger ChangeTracker.StateChanged
_dbContext.SaveChanges();
I found that ChangeTracker.StateChanged is triggered when _dbContext.Entry(student) is called. Then it doesn't get triggered again when _dbContext.SaveChanges() is called. And it also passes the condition above if (e.NewState == EntityState.Modified && e.Entry.Entity is IHasCreationLastModified entity).
My assumption why it is not triggered again when SaveChanges() is called, because there is no new update to the entity after Entity() is called.
This results in the LastModified property being assigned when .Entry(student) is called, instead of when .SaveChanges() is called.
Is there a way to only update LastModified property once when SaveChanges is called on the scenario above?
I suggest that you could override you SaveChanges method in your dbContext. You could refer to below code that I usually use.
public class ForumContext : DbContext
{
public ForumContext(DbContextOptions<ForumContext> options) : base(options)
{
}
//other settings
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
foreach (var entry in ChangeTracker.Entries())
{
switch (entry.State)
{
case EntityState.Added:
((BaseEntity)entry.Entity).AddedDate = DateTime.Now;
((BaseEntity)entry.Entity).LastModified = DateTime.Now;
break;
case EntityState.Modified:
((BaseEntity)entry.Entity).LastModified = DateTime.Now;
break;
case EntityState.Deleted:
entry.State = EntityState.Modified;
entry.CurrentValues["IsDeleted"] = true;
break;
}
}
return base.SaveChanges(acceptAllChangesOnSuccess);
}
I thought you might like to know why you were getting the the events you saw in your question.
When you execute the line student.Name = "New student name";, then, by default, nothing happens because EF Core hasn't called the ChangeTracker.DetectChanges method yet so it doesn't know anything has changed.
But the call to var entry = _dbContext.Entry(student); then runs a version of the ChangeTracker.DetectChanges - see the code below taken from the EF Core code.
public virtual EntityEntry<TEntity> Entry<TEntity>([NotNull] TEntity entity) where TEntity : class
{
Check.NotNull<TEntity>(entity, nameof (entity));
this.CheckDisposed();
EntityEntry<TEntity> entityEntry = this.EntryWithoutDetectChanges<TEntity>(entity);
//My comment - this runs a version of the DetectChanges method.
this.TryDetectChanges((EntityEntry) entityEntry);
return entityEntry;
}
EF Core's Entry method does this because you might ask for the State of the entity and therefore it has to call DetectChanges to make sure its up to date.
Now, it turns out that if you do the following
student.Name = "New student name";
_dbContext.SaveChanges();
Then (in EF Core 5 preview, but I think it is the same in EF Core 3.1) you get two events.
OldState.EntityState == Unchanged, newState.EntityState == Modified - that is triggered by the call to DetectChanges.
OldState.EntityState == Modified, newState.EntityState == Unchanged - that is triggered by SaveChanges when it set the state to say the database matches the entity class.
If you do the following
student.Name = "New student name";
var entry = _dbContext.Entry(student);
_dbContext.SaveChanges();
Then you would get the same events. The DetectChanges would be called twice (once by Entry and once by SaveChanges), but there is no change in the State on the second call the DetectChanges
You can see this in my unit tests in the repo I am writing to support my book Entity Framework Core in Action. I'm writing the section on these events and found your question and though I would answer it.
I hope it helps you understand what is going on, but I should say that the other answers suggesting overriding SaveChanges is a better solution than using these events.
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.
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.
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.
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);
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
Error message: Attaching an entity of type failed because another entity of the same type already has the same primary key value.
Question: How do I attached an entity in a similar fashion as demonstrated in the AttachActivity method in the code below?
I have to assume the "another entity" part of the error message above refers to an object that exists in memory but is out of scope (??). I note this because the Local property of the DBSet for the entity type I am trying to attach returns zero.
I am reasonably confident the entities do not exist in the context because I step through the code and watch the context as it is created. The entities are added in the few lines immediately following creation of the dbcontext.
Am testing for attached entities as specified here:what is the most reasonable way to find out if entity is attached to dbContext or not?
When looking at locals in the locals window of visual studio I see no entities of type Activity (regardless of ID) except the one I am trying to attach.
The code executes in this order: Try -> ModifyProject -> AttachActivity
Code fails in the AttachActivity at the commented line.
Note the code between the debug comments which will throw if any entities have been added to the context.
private string AttachActivity(Activity activity)
{
string errorMsg = ValidateActivity(activity); // has no code yet. No. It does not query db.
if(String.IsNullOrEmpty(errorMsg))
{
// debug
var state = db.Entry(activity).State; // Detached
int activityCount = db.Activities.Local.Count;
int projectCount = db.Activities.Local.Count;
if (activityCount > 0 || projectCount > 0)
throw new Exception("objects exist in dbcontext");
// end debug
if (activity.ID == 0)
db.Activities.Add(activity);
else
{
db.Activities.Attach(activity); // throws here
db.Entry(activity).State = System.Data.Entity.EntityState.Modified;
}
}
return errorMsg;
}
public int ModifyProject(Presentation.PresProject presProject, out int id, out string errorMsg)
{
// snip
foreach (PresActivity presActivity in presProject.Activities)
{
Activity a = presActivity.ToActivity(); // returns new Activity object
errorMsg = ValidateActivity(a); // has no code yet. No. It does not query db.
if (String.IsNullOrEmpty(errorMsg))
{
a.Project = project;
project.Activities.Add(a);
AttachActivity(a);
}
else
break;
}
if (string.IsNullOrEmpty(errorMsg))
{
if (project.ID == 0)
db.Projects.Add(project);
else
db.AttachAsModfied(project);
saveCount = db.SaveChanges();
id = project.ID;
}
return saveCount;
}
This is the class that news up the dbContext:
public void Try(Action<IServices> work)
{
using(IServices client = GetClient()) // dbContext is newd up here
{
try
{
work(client); // ModifyProject is called here
HangUp(client, false);
}
catch (CommunicationException e)
{
HangUp(client, true);
}
catch (TimeoutException e)
{
HangUp(client, true);
}
catch (Exception e)
{
HangUp(client, true);
throw;
}
}
I am not asking: How do I use AsNoTracking What difference does .AsNoTracking() make?
One solution to avoid receiving this error is using Find method. before attaching entity, query DbContext for desired entity, if entity exists in memory you get local entity otherwise entity will be retrieved from database.
private void AttachActivity(Activity activity)
{
var activityInDb = db.Activities.Find(activity.Id);
// Activity does not exist in database and it's new one
if(activityInDb == null)
{
db.Activities.Add(activity);
return;
}
// Activity already exist in database and modify it
db.Entry(activityInDb).CurrentValues.SetValues(activity);
db.Entry(activityInDb ).State = EntityState.Modified;
}
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.
The solution is that
if you had to use GetAll()
public virtual IEnumerable<T> GetAll()
{
return dbSet.ToList();
}
Change To
public virtual IEnumerable<T> GetAll()
{
return dbSet.AsNoTracking().ToList();
}
I resolved this error by changing Update method like below.
if you are using generic repository and Entity
_dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId);
or normal(non-generic) repository and entity , then
_dbContext.Set<TaskEntity>().AddOrUpdate(entityToBeUpdatedWithId);
If you use AddOrUpdate() method, please make sure you have added
System.Data.Entity.Migrations namespace.
I use repository with Update method. In this method I need to check some conditions, finding updating entity in DB before actual update. I use one context for both - Find and Update operations inside one transaction. But when I try to update entity I get an exception:
"Attaching an entity of type 'MyNamespace.MyEntityType' failed because another entity of the same type already has the same primary key value"
public void Update(SomeType entity)
{
using (var context = new MyEntities())
{
using (var transaction = context.Database.BeginTransaction(IsolationLevel.RepeatableRead))
{
try
{
// check conditions
var root = context.MyEntitySet
.Where(e => e.Parent == null); // entity.Parent == null
if(root ...)
; // return
// HERE I got an error described above
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}
}
I need to load entity with same Id from DB first, check some conditions and if ok, update entity. But after "Get" entity attaches to ChangeTracker with "Detached" state and when I try to change state to "Modified" I got an error.
I've already tried:
if (IsAttached(entity, out attachedEntity))
context.Entry(attachedEntity).CurrentValues.SetValues(entity);
but my original entity object stays unchanged with DB generated values. State = EntityState.Modified works as I need, but how can I remove preloaded entity from ChangeTracker?
What is the proper and fast way to save combined new and modified detached POCO entities?
I was thinking about these methods:
private void Method_2(IList<Entity> entities) //detached entities
{
//This method is using SELECT to check if entity exist
using (var context = new ModelContainer())
{
foreach (Entity entity in entities)
{
var foundEntity = context.CreateObjectSet<Entity>().SingleOrDefault(t => t.Id == entity.Id);
context.Detach(foundEntity); //Remove it from ObjectStateManager
if (foundEntity != null)//It is modified entity
{
context.AttachTo("EntitySet", entity); //Attach our entity
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified); //We know it exists
}
else//It is new entity
{
context.CreateObjectSet<Entity>().AddObject(entity);
}
}
context.SaveChanges();
}
}
private void Method_1(IList<Entity> entities) //detached entities
{
//This method doesn't select anything from DB, but i have ta call Savechanges after each object
using (var context = new ModelContainer())
{
foreach (Entity entity in entities)
{
try
{
context.AttachTo("EntitySet", entity);
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
context.SaveChanges();
}
catch (OptimisticConcurrencyException)
{
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Added);
context.SaveChanges();
}
}
}
}
When you are working in detached environment you have to know which entity was added and which is modified - it is your responsibility to keep this information and provide it to ObjectContext.
Well i agree with this statement if you found yourself in situation when you need to use EF code like this in EF definitely something is wrong with you decision. I have chosen wrong tool for this job.
When you are working in detached environment you have to know which entity was added and which is modified - it is your responsibility to keep this information and provide it to ObjectContext.
The very easy way is:
foreach (var entity in entities)
{
if (entity.Id == 0) // 0 = default value: means new entity
{
// Add object
}
else
{
// Attach object and set state to modified
}
}
The example requires that you have some db auto-generated primary key (Id).
Your Method 2 is possible with some modifications. It is not needed to detach entity when you load it. Instead use ApplyCurrentValues. The approach with loading entity first is very usefull when you decide to work with object graphs instead of single entity. But in the case of object graph you have to do synchronization manually. ApplyCurrentValues works only for scalar (non navigation) properties. You can try to futher optimize your method to load needed enitites in single roundtrip to database instead of loading entities one by one.
Your Method 1 is terrible solution. Using exceptions raised on database server to control program flow is bad approach.
I agree with #Ladislav - Method_1 is a bad approach. Let the database raise exceptions which are caught by EF - don't try and swallow these exceptions yourself.
Your on the right track with Method 1.
Here is how i do it - as i also have a detached context (POCO's, no change tracking, ASP.NET MVC).
BLL Interface: (note i have TPT in my model, hence generics. "Post" is abstract)
void Add(Post post);
void Update<TPost>(TPost post) where TPost : Post, new();
The new() constraint is crucial - you'll see why shortly.
I won't show how i do "Add", because it's simple as you think - AddObject(entity);
The "Update" is the tricky part:
public class GenericRepository<T> : IRepository<T> where T : class
{
public void Update<T2>(T2 entity) where T2: class, new()
{
var stub = new T2(); // create stub, now you see why we need new() constraint
object entityKey = null;
// ..snip code to get entity key via attribute on all domain entities
// once we have key, set on stub.
// check if entity is already attached..
ObjectStateEntry entry;
bool attach;
if (CurrentContext.ObjectStateManager.TryGetObjectStateEntry(CurrentContext.CreateEntityKey(CurrentContext.GetEntityName<T>(), stub), out entry))
{
// Re-attach if necessary.
attach = entry.State == EntityState.Detached;
}
else
{
// Attach for first time.
attach = true;
}
if (attach)
CurrentEntitySet.Attach(stub as T);
// Update Model. (override stub values attached to graph)
CurrentContext.ApplyCurrentValues(CurrentContext.GetEntityName<T>(), entity);
}
}
And that works for me.
As for the entity key, i have used attributes on my domain classes. An alternative (which i'm about to move to), is have all my domain entities implement an interface, which specifies that all domain entities must have a property called "EntityKey". Then i'll use that interface on my constraints. Basically, i needed a dynamic way to create stub entities in a generic repository.
I don't personally like the idea of "checking the ID, if its > 0 then it's an update". Because i'm working with ASP.NET MVC, if i (or another developer) forgets to bind the ID to the View, it won't be passed through, so even though it may be an update, because the ID == 0 it will be added.
I like to be explicit about the operations. This way, i can perform Add/Update seperate validation logic.
Perhaps take a look at Self Tracking POCO entities. IMHO they are perfect for any scenario that requires the entity to be separated from the context. It takes care of all the plumbing code for you.