0

I have the following code, where I receive an incomplete entity from the frontend, make changes and update it. Afterwards, I retrieve the same entity, now complete, from the database, make new changes and update it again... (Business rules have been removed for better understanding.)

`

public async Task<SeparationRequest> CollectSignature(SeparationRequest separationRequest)
        {
            await base.UpdateAsync(separationRequest);
                 
            int IdSeparationRequestAux = separationRequest.Id;
            separationRequest = null;

            var newSeparationRequest = await GetByIdAsync(IdSeparationRequestAux);

            await base.UpdateAsync(newSeparationRequest );

            return newSeparationRequest; 
        }

`

This is the UpdateAsync Method: `

 public virtual async Task<TEntity> UpdateAsync(TEntity entity)
        {
            Repository.Update(entity);
            await _unitOfWork.SaveChangesAsync();

            return entity;
        }

`

This is the GetByIdASync Method: `

public override async Task<SeparationRequest> GetByIdAsync(object id)
        {
            var apps = await Repository.GetFirstOrDefaultAsync(predicate: x => x.Id == (int)id, disableTracking: true,
                include: x => x.AsNoTracking().Include(x => x.SeparationRequestStatus)
                               .Include(x => x.Itens)
                               .ThenInclude(x => x.SeparationRequestItemStatus));

            return apps;
        }

`

The GetByIdAsync method calls the following method: Get FirstOrDefaultAsync `

public virtual async Task<TEntity> GetFirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
            bool disableTracking = true,
            bool ignoreQueryFilters = false)
        {
            IQueryable<TEntity> query = _dbSet;

            if (disableTracking)
            {
                query = query.AsNoTracking();
            }

            if (include != null)
            {
                query = include(query);
            }

            if (predicate != null)
            {
                query = query.Where(predicate);
            }

            if (ignoreQueryFilters)
            {
                query = query.IgnoreQueryFilters();
            }

            if (orderBy != null)
            {
                return await orderBy(query).FirstOrDefaultAsync();
            }
            else
            {
                return await query.FirstOrDefaultAsync();
            }
        }

`

the first updateAsync is executed successfully, but when hitting the second UpdateAsync the following exception is triggered:

System.InvalidOperationException: 'The instance of entity type 'SeparationRequest' 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.'

Does anyone have any idea what I can do? obs: I'm using AddScopped and with AddTransient I got the same error

  • Why are you doing the 2nd update to "separationRequest" after you null that variable out? Once you do the save operation in the "UpdateAsync", you should be able to safely ignore the "separationRequest" variable altogether (as in not nulling it or re-saving it) to query for the object with the `include`-ed values. – computercarguy Nov 28 '22 at 21:41
  • sorry, there was a mistake in this code, in the second update it is sending newSeparationRequest and not separationRequest – DeividCamargos Nov 29 '22 at 12:42
  • Why are you saving something that you just queried from the DB and aren't changing? That's unnecessary resources used and not doing this will avoid the behavior you are experiencing. Also, what's the point of setting it as "no tracking", then reattaching it to the DB, especially when there aren't any changes? – computercarguy Nov 29 '22 at 20:24

1 Answers1

0

As your DBContext is 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.

Instead of a Singleton repository, use 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 you will then get a new instance per request and your updates should work fine.

Reference:

instance of entity type cannot be tracked because another instance with same key value is tracked