Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF Core 'another instance is already being tracked'

Tags:

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

like image 496
Marian Simonca Avatar asked May 21 '19 14:05

Marian Simonca


People also ask

How do I turn off Entity Framework tracking?

In Entity Framework, change tracking is enabled by default. You can also disable change tracking by setting the AutoDetectChangesEnabled property of DbContext to false. If this property is set to true then the Entity Framework maintains the state of entities.

When attaching existing entities ensure that only one entity instance with a given key value is attached consider using?

When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.

How do you get entities back from a query without getting tracked by the context?

The AsNoTracking() extension method returns a new query and returned entities do not track by the context. It means that EF does not perform any additional task to store the retrieve entities for tracking. We can also change the default behavior of tracking at context instance level.

What is tracking in EF core?

Tracking behavior controls if Entity Framework Core will keep information about an entity instance in its change tracker. If an entity is tracked, any changes detected in the entity will be persisted to the database during SaveChanges() .


2 Answers

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;
    }
}
like image 194
Joe Audette Avatar answered Sep 28 '22 05:09

Joe Audette


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!!!!!
like image 30
Haktan Enes Biçer Avatar answered Sep 28 '22 05:09

Haktan Enes Biçer