简体   繁体   中英

The instance of entity type '' cannot be tracked

I am sending an authorization request, in the method controller for authorization, I am trying to update the entity for the user who has passed authorization, but I have an error:

The instance of entity type 'SUsers' 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.

stack used

asp core 2.2, spa, vue, pwa, jwt, automapper 8.8.4,Microsoft.EntityFrameworkCore 2.2.4

Versions

  • Net core 2.2
  • Microsoft.EntityFrameworkCore 2.2.4
  • Microsoft.EntityFrameworkCore.InMemory 2.2.4
  • Microsoft.EntityFrameworkCore.Design 2.2.4
  • Microsoft.EntityFrameworkCore.SqlServer 2.2.4

0, DI

    public static class StartupExtension
    {

    public static IServiceCollection AddDependencies(this IServiceCollection _iServiceCollection, IConfiguration AppConfiguration )
    {

              #region Data

              string ids = System.Guid.NewGuid().ToString();

            _iServiceCollection.AddDbContext<BaseDbContext, FakeDbContext>(opt =>
            {
                opt.UseInMemoryDatabase(ids);
            });

            _iServiceCollection.AddScoped<IBaseDbContext>(provider => provider.GetService<BaseDbContext>());

            #endregion

            #region AutoMapper

            var config = new MapperConfiguration(cfg => {
                cfg.AddMaps("PWSPA.WEB", "PWSPA.BLL");
            });

            config.AssertConfigurationIsValid();
            #endregion

            #region Repository

            _iServiceCollection.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));
            _iServiceCollection.AddScoped<IUnitOfWork, UnitOfWork>();
            #endregion

            #region service

            #region mapper service
            _iServiceCollection.AddScoped(typeof(IGenericMapperService<,>), typeof(GenericMapperService<,>));
            _iServiceCollection.AddScoped(typeof(IMapperService), typeof(MapperService));
            #endregion
            _iServiceCollection.AddScoped<IAuthService, AuthService>();
            #endregion

            return _iServiceCollection;
    }

}

1. Api Controller

    public class AuthController : BaseApiController
    {
        private readonly ILogger _log;
        private readonly SecuritySettings _config;
        private readonly IUserVerify _signInMgr;
        private readonly IAuthService _iAuthService;

        [AllowAnonymous]
        [HttpPost("login")]
        public IActionResult Login([FromBody] RequestTokenApiModel model)
        {
            try
            {
                SUsersDTO user = null;

                user = _iAuthService.SingleOrDefault(u => 
    u.WindowsLogin.ToLower() == "guest");

                user.WindowsLogin = "guest";

                /*
                The instance of entity type 'SUsers' 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.
                */

                countUpdate = _iAuthService.Update(user);

            }
            catch (ArgumentException ex)
            {
                return BadRequest(ex.Message);
            }
            catch (Exception ex)
            {
                _log.LogError(ex, ex.Message);
                return StatusCode(500, ex.Message);
            }
        }
    }

2. Service

    public class AuthService : ServiceBase<SUsers, SUsersDTO>, IAuthService
    {

        public AuthService(IUnitOfWork uow, IMapperService MapperService) : base(uow, MapperService)
        {
            Repository.Query().Include(u => u.Role).Load();
        }
        ...
   }

 public class ServiceBase<TModel, TModelDTO> : IGenericService<TModelDTO> where TModel : class where TModelDTO : class
    {
        private readonly IUnitOfWork db;
        private readonly IMapperService _MapService;
        private readonly IGenericRepository<TModel> genericRepository;
        private readonly IGenericMapperService<TModel, TModelDTO> genericMapService;

        public ServiceBase(IUnitOfWork uow, IMapperService iMapperService)
        {
            _MapService = iMapperService;
            db = uow;
            genericRepository = uow.Repository<TModel>();
            genericMapService = _MapService.Map<TModel, TModelDTO>();
        }
        protected virtual Type ObjectType => typeof(TModel);
        protected virtual IGenericRepository<TModel> Repository => genericRepository;
        protected virtual IMapperService MapService => _MapService;
        protected virtual IGenericMapperService<TModel, TModelDTO> Map => genericMapService;
        protected virtual IUnitOfWork Database => db;

        ...
             public int Update(TModelDTO entityDto)
        {
            var entity = Map.For(entityDto);
            return Repository.Update(entity);
        }

}

3. Repos

    public class GenericRepository<TEntity> :
        IGenericRepository<TEntity> where TEntity : class
    {
        private readonly IBaseDbContext _context;
        private readonly IUnitOfWork _unitOfWork;
        private readonly string errorMessage = string.Empty;

        public GenericRepository(IBaseDbContext context, IMapper _iMapper) //: base(context, _iMapper)
        {
            _context = context;
            _unitOfWork = new UnitOfWork(context, _iMapper);
        }
        public Type ObjectType => typeof(TEntity);

        protected virtual IBaseDbContext DbContext => _context;

        protected virtual DbSet<TEntity> DbSet => _context.Set<TEntity>();
        ...
        public int Update(TEntity updated)
        {
            if (updated == null)
            {
                return 0;
            }

            DbSet.Attach(updated);
            _context.Entry(updated).State = EntityState.Modified;
            return Save();
        }
        ...
        private int Save()
        {
            try
            {
                return _unitOfWork.Commit();
            }
            catch (DbUpdateException e)
            {
                throw new DbUpdateException(e.Message, e);
            }
        }

4. UnitOfWork

  public class UnitOfWork : IUnitOfWork
    {
        private readonly IBaseDbContext _dbContext;
        private readonly Dictionary<Type, object> _repositories = new Dictionary<Type, object>();
        private readonly IMapper _iMapper;


        public Dictionary<Type, object> Repositories
        {
            get => _repositories;
            set => Repositories = value;
        }

        public UnitOfWork(IBaseDbContext dbContext, IMapper _iMapper)
        {
            _dbContext = dbContext;
            this._iMapper = _iMapper;
        }

        public IGenericRepository<TEntity> Repository<TEntity>() where TEntity : class
        {
            if (Repositories.Keys.Contains(typeof(TEntity)))
            {
                return Repositories[typeof(TEntity)] as IGenericRepository<TEntity>;
            }

            IGenericRepository<TEntity> repo = new GenericRepository<TEntity>(_dbContext, _iMapper);
            Repositories.Add(typeof(TEntity), repo);
            return repo;
        }

        public EntityEntry<TEintity> Entry<TEintity>(TEintity entity) where TEintity : class
        {
            return _dbContext.Entry(entity);
        }
        ...
}

an exception occurs in the repository

        public int Update(TEntity updated)
        {
            if (updated == null)
            {
                return 0;
            }
           /*
on line DbSet.Attach(updated) an exception occurs
*/
            DbSet.Attach(updated);
            _context.Entry(updated).State = EntityState.Modified;
            return Save();
        }

I think this is due to the mapping in the service that uses repository

      public int Update(TModelDTO entityDto)
        {
            var entity = Map.For(entityDto);
            return Repository.Update(entity);
        }

Steps to reproduce

  1. clone https://github.com/UseMuse/asp-core-2.2-clean.git
  2. build solution, start progect PWSPA.WEB
  3. log in: login - guest, pass - any charts
  4. in api controller AuthController, method Login, exception line 90

Expected behavior:

entity update

error msg

The instance of entity type 'SUsers' 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.

StackTrace

at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap 1.ThrowIdentityConflict(InternalEntityEntry entry) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap 1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode node, Boolean force) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode node, TState state, Func 3 handleNode) at Microsoft.EntityFrameworkCore.DbContext.SetEntityState[TEntity](TEntity entity, EntityState entityState) at PWSPA.DAL.Repositories.GenericRepository 1.Update(TEntity updated) in D:\\repos\\asp-core-2.2-clean2\\PWSPA.DAL\\Repositories\\GenericRepository.cs:line 99 at PWSPA.BLL.Services.ServiceBase`2.Update(TModelDTO entityDto) in D:\\repos\\asp-core-2.2-clean2\\PWSPA.BLL\\Services\\ServiceBase.cs:line 208 at PWSPA.API.Controllers.AuthController.Login(RequestTokenApiModel model) in D:\\repos\\asp-core-2.2-clean2\\PWSPA.WEB\\API\\AuthController.cs:line 90

You've made a rookie mistake here of essentially just dumping all information you can think of, and yet, you've ironically missed the only piece that actually matters: the code behind your _iAuthService . Post only code that is directly related to the issue. If we need something else, we can always ask for it. And, in that respect, post all code that is directly related to the issue. If the error is coming out of a custom service class you wrote, post that service class.

That said, the error you're getting boils down to the following situation. At some point, you query an entity, which adds it to the context's object tracking. Then, you later attempt to update a non-tracked version of that entity, instead of the one you queried. That could occur from receiving it from the model-binder (ie it's a param on the action), literally instantiating one with new , or simply using a different instance of the context to retrieve it (and saving it to another instance).

Based on the code you have provided, my money is on the last one. You likely aren't handling the context properly in your service class, and you're getting the entity to modify from one instance of the context and attempting to update it with a different instance of the context. Your context should always be injected, to ensure that you're always using the same instance across the lifetime (the request). In other words, if you're doing a using (var context = new MyContext()) or really any new MyContext() , that's your problem.

Since I use automapper, my problem was solved using AutoMapper.Collection

my resolve issue

1. DI and init AutoMapper

    //using AutoMapper;
    //using AutoMapper.Configuration;
    //using AutoMapper.EquivalencyExpression;
    //using AutoMapper.Extensions.ExpressionMapping;

    services.AddAutoMapper (assemblyes);

    MapperConfigurationExpression configExpression = new MapperConfigurationExpression ();
    configExpression.AddCollectionMappers ();
    configExpression.AddExpressionMapping ();
    configExpression.UseEntityFrameworkCoreModel <BaseDbContext> (services.BuildServiceProvider (). 
    CreateScope (). ServiceProvider);
    configExpression.AddMaps (assemblyes);
    Mapper.Initialize (configExpression);
    Mapper.Configuration.AssertConfigurationIsValid ();

2. My extensions for Repos


   //using AutoMapper.EntityFrameworkCore;
   //using Microsoft.EntityFrameworkCore;

    public static class RepoExtensions
    {
        public static TModel InsertOrUpdate<TModel, TModelDto>(this IRepository repository, TModelDto modelDto) where TModel : BaseEntity where TModelDto :
     BaseEntityDTO
        {
            TModel model = repository.DbSet<TModel>().Persist().InsertOrUpdate(modelDto);
            repository.Save();
            return model;
        }
        public static async Task<TModel> InsertOrUpdateAsync<TModel, TModelDto>(this IRepository repository, TModelDto modelDto) where TModel : BaseEntity where TModelDto :
      BaseEntityDTO
        {
            TModel model = repository.DbSet<TModel>().Persist().InsertOrUpdate(modelDto);
            await repository.SaveAsync();
            return model;
        }
    }

3. An example of updating an entity by an entity in a service

before

 public int Update(TModelDTO entityDto) { var entity = Map.For(entityDto); return Repository.Update(entity); } 

after

    public TModel Update(TModelDTO entityDto)
     {
         return   Repository.InsertOrUpdate<TModel, TModelDTO>(entityDto);
     } 

ps the repository referred to for the example did not update

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM