EF Core 'another instance is already being tracked'

26,237

Solution 1

By default when you retrieve entities they are tracked and since they are tracked you could just call SaveChanges and not call Update. You can also retrieve entities without tracking them by using .AsNoTracking()

calling Update is needed if not tracked already, so if you use AsNoTracking then you do need to use Update before SaveChanges

public IQueryable<Anomaly> GetAll()
{    return _context.Anomalies
    .Include(a => a.Asset)
    .Include(a => a.Level);
}

public async Task<Anomaly> GetAnomaly(int anomalyId, User user)
{
    var anomaly = await GetAll()
        .AsNoTracking()
        .FirstOrDefaultAsync(a => a.Id == anomalyId);

    return anomaly;
}

You can also check if the entity is tracked to know whether to call Update or not:

using (var transaction = _context.Database.BeginTransaction())
{
    try
    {

        bool tracking = _context.ChangeTracker.Entries<Anomaly>().Any(x => x.Entity.Id == anomaly.Id);
        if (!tracking)
        {
            _context.Anomalies.Update(anomaly);
        }

        _context.SaveChanges();

        transaction.Commit();
    }
    catch (Exception ex)
    {
        transaction.Rollback();
        throw;
    }
}

Solution 2

I was trying to update the same data without noticing. It took me ten hours to realize that. If you have duplicate values like mine,i suggest remove that data... The person who reading this answer may have tried all the solutions on the internet like me, ruined the project, stuck the same error, and omitted duplicate data just like me. You are not alone my friend.

model.GroupBy(gb => gb.ID).Select(s=>s.First()).ToList();//remove duplicates!!!!!
Share:
26,237

Related videos on Youtube

Marian Simonca
Author by

Marian Simonca

I am a young software engineer currently working and living in Cluj-Napoca, Romania.

Updated on August 29, 2022

Comments

  • Marian Simonca
    Marian Simonca over 1 year

    I have a problem updating an entity in .Net Core 2.2.0 using EF Core 2.2.3.

    An error occurred while saving changes. Error details: The instance of entity type 'Asset' 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

    This is how the DB Context is registered:

    services.AddDbContext(options =>

    options.UseSqlServer(Configuration.GetConnectionString("DbConnection")), ServiceLifetime.Scoped);
    

    The Scoped lifetime is set by default but I wrote it to be more easy to understand.

    The Anomaly object is got like this:

    public IQueryable<Anomaly> GetAll()
        {return _context.Anomalies.Include(a => a.Asset).Include(a => a.Level)
    }
    
    public async Task<Anomaly> GetAnomaly(int anomalyId, User user)
    {
        var anomaly = await GetAll()
            .FirstOrDefaultAsync(a => a.Id == anomalyId);
    
        return anomaly;
    }
    

    And the Update() method looks like this:

    using (var transaction = _context.Database.BeginTransaction())
    {
        try
        {
            _context.Anomalies.Update(anomaly);
            _context.SaveChanges();
    
            transaction.Commit();
        }
        catch (Exception ex)
        {
            transaction.Rollback();
            throw;
        }
    }
    

    It contains some checks before this transaction, but none relevant enough in this context.

    This is where I get the error with instance already being tracked. I can't understand how this happens .. If the context is Scoped, then

    ... "a new instance of the service will be created for each scope", in this case, for each request

    If my context on the PUT request is different from the context of the GET request, how is the entity already being tracked? How does this work at the most basic levels?

    The only way to make it work is to set the state for all entries from the ChangeTracker to EntityState.Detached. Then it works.. but it makes no sense, at least to my current knowledge..

    I found this question but with no valid answer, only with workarounds and assumptions about how EF does the tracking.


    UPDATE Here is a link to bitbucket with a sample recreating this problem: EF Core Update Sample

    I serialized the objects retrieved from the context.

    With Tracking on the LEFT <====> With NO tracking on the RIGHT With Tracking on the LEFT <====> With NO tracking on the RIGHT

    • ilkerkaran
      ilkerkaran almost 5 years
      there is more than 1 context instance per request. As you mentioned. using Detach is not a valid solution here you need to find the root cause. How you inject _context type?
    • Ivan Stoev
      Ivan Stoev almost 5 years
      "It contains some checks before this transaction, but none relevant enough in this context." Are you sure? Is _context.Set<Asset>().Local.Count == 0 true before calling _context.Anomalies.Update(anomaly);?
    • Marian Simonca
      Marian Simonca almost 5 years
      @Ivan, it seems that _context.Set<Asset>().Local.Count == 0 is false. I ended up commenting all the code before that check and it still is false..
    • Marian Simonca
      Marian Simonca almost 5 years
      @ilkerkaran, I inject the _context in the constructor like this: public AnomalyManager(SAMSDbContext context){_context = context;} where _context is a private readonly field. In the original question I posted how the DbContext is registered in the Startup.cs
  • Marian Simonca
    Marian Simonca almost 5 years
    I tried this but doesn't work. Even with .AsNoTracking() before `Include(), even after it, same result ..
  • Joe Audette
    Joe Audette almost 5 years
    strange it works for me, I've updated my answer to show how to detect if the entity is tracked which can be used to decide if to call Update or not.
  • Joe Audette
    Joe Audette almost 5 years
    I've edited my answer, it seems .AsNoTracking should be after all the .Includes
  • Marian Simonca
    Marian Simonca almost 5 years
    I checked that question, it's not working for me. I'm not sure if it's because .NET Core, or why.. Tried with the QueryTrackingBehavior field as well, no improvements. I start to belive something is wrong on another level, maybe my setup is wrong, my config or something like this.. Will come with an update when I find something
  • Marian Simonca
    Marian Simonca almost 5 years
    I created a sample and updated my question. It seems that it works if I have fewer entities and I add .AsNoTracking(). But that is not a solution since I will need the ChangeTracker to Audit the Update operation and log all changes made to some specific entities
  • Joe Audette
    Joe Audette almost 5 years
    as mentioned before if you leave out .AsNoTracking, then you don't need to call Update at all, you only need to call SaveChanges and it will update the entity since it is already tracking the changes on the entity.
  • Marian Simonca
    Marian Simonca almost 5 years
    I know Joe, I removed the Update call but with no success. Without it no changes reach the database..
  • Marian Simonca
    Marian Simonca over 4 years
    Good advice, thank you! It seems that this will be a collection of possible solutions for a problem that we all face at some point.
  • Gert Arnold
    Gert Arnold over 2 years
    It's not obvious how that would cause this error which is all about duplicated primary keys. A reference is never (part of) a primary key.