Search code examples
c#postgresqlentity-frameworklinq

The instance of entity type cannot be tracked even with AsNoTracking


I receive an error in test environment, but not on the prod environment even as no tracking has already been placed.

Error "GetUserAsync: System.InvalidOperationException: The instance of entity type 'UserEntity' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

Code:

internal async Task<UserEntity> GetUserAsync(CancellationToken cancellationToken)
{
    var result = await _userDataQuery.GetLatestUser(cancellationToken);
}

I already added AsNoTracking:

public async Task<UserEntity> GetLatestUser(CancellationToken cancellationToken)
{
    var result = await this.Entities
                           .OrderByDescending(m => m.Id)
                           .AsNoTracking().FirstOrDefaultAsync();
    await Context.Entry(result).ReloadAsync();

    return result;
}

Not sure if this help but the I also notice the pg_sequences on postgreSQL between the two environments are different for User Table and both of them only have 1 record in the table

Test Environment:

last_value = 2

Prod Environment:

last_value = 1

Solution

  • This error means that within the lifetime scope of the DbContext (Context) that entity has already been loaded and is being tracked. This could be directly via a different method call, or indirectly by being eager or lazy loaded as part of another entity load. Reload will attempt to track the entity which will result in an error where another matching instance is already tracked.

    From what I can make of what you are trying to do, you probably don't want to use AsNoTracking() since you cannot guarantee that the entity hasn't already been tracked. Load it as-per normal.

    var result = await this.Entities
                           .OrderByDescending(m => m.Id)
                           .FirstAsync();
    await Context.Entry(result).ReloadAsync();
    

    Whenever you use an *OrDefault() flavour, you must handle the fact that there may be no items in the set. If you expect at least one, use First.

    The downside here is that Reload will be called even when the desired item is already freshly loaded from the DB. Since we cannot assume the desired item is or isn't already tracked, about the best option would probably be:

    var id = this.Entities
                 .OrderByDescending(m => m.Id)
                 .Select(x => x.Id)
                 .FirstAsync();
    
    var result = this.Entities.Local
                           .Where(m => m.Id == id)
                           .SingleOrDefault();
    if (result != null)
        await Context.Entry(result).ReloadAsync();
    else
        result = this.Entities
            .Where(m => m.Id == id)
            .Single();
    

    This code looks for a particular item in the local tracking cache, if found it will reload it, otherwise it will load the current item from the DB.

    This approach (minus selecting the ID from the DB) would apply in any other situations where you know ahead of time what specific entity you want rather than relying on something like FirstOrDefault and want to ensure any already tracked entity is available and refresh it.