The instance of entity type 'x' cannot be tracked because another instance with the key value '{Id: 6}' is already being tracked

14.2k views Asked by At

I am working a asp .netcore 6.0 clean architecture project.

When I try to update a site, I got this error,

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

Before We use services, in there this same code worked fine. Now we move to clean architecture(CQRS and Mediatr). I used same update code but I got this error.

.AsNoTracking() I tried with var result = await _DbContext.SiteCodes.FindAsync(request.Id).AsNoTracking(); this line,

But got error as,

'ValueTask<SiteCode?>' does not contain a definition for 'AsNoTracking' and no accessible extension method 'AsNoTracking' accepting a first argument of type 'ValueTask<SiteCode?>' could be found (are you missing a using directive or an assembly reference?) [Application]

Here is my codes

UpdateSiteCommandHandler.cs

public async Task<SiteCode> Handle(UpdateSiteCommand request, CancellationToken cancellationToken)
    {
        var result = _mapper.Map<SiteCode>(request);

        _DbContext.SiteCodes.Update(result);  // goes to exception after this line

        await _DbContext.SaveChangesAsync(cancellationToken);

        return result;
    }

GetSiteByIdQueryHandler.cs

public async Task<SiteCode> Handle(GetSiteByIdQuery request, CancellationToken cancellationToken)
    {
        
        var result = await _DbContext.SiteCodes.FindAsync(request.Id);

        if (result == null)
        {
            throw new NotFoundException(nameof(SiteCode), request.Id);
        }

        return result;

    }

controller

public async Task<IActionResult> Update(int id, [FromBody] UpdateSiteCommand command)
        {
            command.Id = id;

            var siteCode = await _mediator.Send(new GetSiteByIdQuery(Id: id));

            var result = await _mediator.Send(command);
            
            return Ok(result);

        }

DependencyInjection.cs

public static class DependencyInjection
{
    public static IServiceCollection AddApplication(this IServiceCollection services)
    {
        services.AddAutoMapper(Assembly.GetExecutingAssembly());
        services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
        services.AddMediatR(Assembly.GetExecutingAssembly());
        services.AddTransient(typeof(IPipelineBehavior<,>), typeof(PerformanceBehaviour<,>));  // I tried with AddScoped() But not work
       
        return services;
    }

}

in DbContext

public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            var entries = ChangeTracker
                .Entries()
                .Where(e => e.Entity is AuditableEntity && (
                        e.State == EntityState.Added
                        || e.State == EntityState.Modified));

            foreach (var entityEntry in entries)
            {
                if (entityEntry.State == EntityState.Added)
                {
                    ((AuditableEntity)entityEntry.Entity).CreatedAt = DateTime.UtcNow;
                    ((AuditableEntity)entityEntry.Entity).CreatedBy = _httpContextAccessor?.HttpContext?.User?.Identity?.Name ?? null;
                }
                else
                {
                    Entry((AuditableEntity)entityEntry.Entity).Property(p => p.CreatedAt).IsModified = false;
                    Entry((AuditableEntity)entityEntry.Entity).Property(p => p.CreatedBy).IsModified = false;
                }

                ((AuditableEntity)entityEntry.Entity).ModifiedAt = DateTime.UtcNow;
                ((AuditableEntity)entityEntry.Entity).ModifiedBy = _httpContextAccessor?.HttpContext?.User?.Identity?.Name ?? null;
            }

            var result = await base.SaveChangesAsync(cancellationToken)

         return result;
        }

Anyone has idea how can solve this issue?

2

There are 2 answers

4
Svyatoslav Danyliv On BEST ANSWER

EF Core uses ChangeTracker to detect changes in loaded entities, and better (not faster) solution is to load entity for update.

public async Task<SiteCode> Handle(UpdateSiteCommand request, CancellationToken cancellationToken)
{
    // the following code will load entitiy if it is still not loaded.
    var dbRequest = await _DbContext.SiteCodes.FindAsync(request.Id);

    if (dbRequest == null)
        throw new Exception("Not found");

    _mapper.Map<SiteCode>(request, dbRequest);

    await _DbContext.SaveChangesAsync(cancellationToken);

    return result;
}
2
Yago Oliveira On

Try to add the method AsNoTracking() after SiteCodes to your query in Handler (GetSiteByIdQueryHandler.cs).

public async Task<SiteCode> Handle(GetSiteByIdQuery request, CancellationToken cancellationToken)
{
    
    var result = await _DbContext.SiteCodes.AsNoTracking().FindAsync(request.Id);

    if (result == null)
    {
        throw new NotFoundException(nameof(SiteCode), request.Id);
    }

    return result;
}