33
votes

I'm using generic repository pattern in asp.net core 2.0 which can not dispose repository object, when I am going to update the entry its updated for one time successfully but when I am trying to update for more then once it throws the following exception:

The instance of entity type 'Company' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.

public ActionResult Edit(Int64 id, Company collection)
{
    try
    {
        // TODO: Add update logic here
        interfaceobj.updateModel(collection);
        interfaceobj.Save();
        return RedirectToAction(nameof(Index));
    }
    catch(Exception ex)
    {
        throw ex;
    }
}
5
Read How to Ask and create a minimal reproducible example. You shouldn't reuse DbContext between requests. - CodeCaster
It would be great if you had provided some code instead of saying I've implemented Repository Pattern. The problem seems to exist in how you've implemented that! This question is not helping others with the same problem. - Shahryar Saljoughi
There's nothing in the code that shows what EF does, it's all hidden behind custom code. This can't possibly be answered. What is interfaceobj? What is the status of the code? Why the TODO? Etc. - Gert Arnold

5 Answers

35
votes

Your DB Context is being shared by multiple requests, meaning that the entity you're editing has been tracked already.

This is likely because your repository service is a Singleton rather than Scoped, and so your DB Context is being reused with the entity being tracked when it's pulled out, and then put back in to the same instance of the DB Context.

What you should be doing instead is having a Scoped Repository, which means that a new instance will be created for each request. Likewise, you will also have a per-request DB Context.

When you register your service it will be using services.AddSingleton<..>

Change this to services.AddScoped<..>, when you inject it into your controller you will then get a new instance per request and your updates should work fine.

21
votes

This will help you!

AsNoTracking()

By default, EntityFramework tracks changes on entities that are queried and saves changes when you call SaveChanges() on the context.

If it is already tracking product:42, and you add or attach a product with the Id:42 that it hasn't seen, there is ambiguity as to which represents the source of truth.

AsNoTracking produces objects which are not added to the change tracking mechanism, which you should use if you aren't using the change tracking features, because it's faster and makes your intent more explicit.

Most often, this is a scope issue. You don't expect there to be something in the cache, but surprise, there is - usually because of some global variable or a context which was not disposed - Both very common. When you find the offending DbContext, either wrap it in a 'using' or add AsNoTracking.

What difference does .AsNoTracking() make?

6
votes

You need Detached Entry-

Entry<CustomerBaseInfo>(data).State = EntityState.Detached;
3
votes

Just like Rudi Visser suggested, you can face this issue when using one entity across multiple data contexts. In my case, it was something similar to this:

User user = await DataContext.Users.SingleOrDefaultAsync(u => u.Id == "SomeId");

// Switch to UserManager *_*
var res = await UserManager.ResetPasswordAsync(user, token, password); // Exception thrown

A simple solution would be to try to stick to one DataContext, because UserManager seems to do some attaching and detaching (maybe) in the background. For example:

User user = await UserManager.FindByIdAsync("SomeId");
var res = await UserManager.ResetPasswordAsync(user, token, password);
0
votes

I am experiencing with same problem and googled and here is my thought

Entity framework has change tracker and entities are stored and referenced in the change tracker using their primary key. More precisely, dbcontext has a secret dictionary and it only allow to store one object of same type and same primary key. It can not track two object of same type and same primary key. Here is my sample code.

var model = new EaEnrollment()
        {
            Id = 1,
            UserId = 2,
            AllocationType = "Individual",
            Status = "In Progress"
        };
        _repoEaEnrollment.Update(model);
        //here is the some other code that meets my businees requirement
        //and changes the status to Completed and trying to update type a

        model.Status = "Completed";
        _repoEaEnrollment.Update(model); // exception .ie. it can not track 
                                         //  two distinct  instance. They are distinct becuase  
                                         //they have same typeand same primary key

I solved by detaching first instance from change tracker. Here is the solution code..

 var model = new EaEnrollment()
        {
            Id = 1,
            UserId = 2,
            AllocationType = "Individual",
            Status = "In Progress"
        };
        _repoEaEnrollment.Update(model);
        _repoEaEnrollment.SaveChanges();
        //here is the some other code that meets my businees requirement
        //and changes the status to Completed and trying to update type a

        //detach first instance from change tracker
        _repoEaEnrollment.Detach(model);

        //Update again
        model.Status = "Completed";
        _repoEaEnrollment.Update(model); // no exception