Reputation: 21
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.
asp core 2.2, spa, vue, pwa, jwt, automapper 8.8.4,Microsoft.EntityFrameworkCore 2.2.4
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;
}
}
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);
}
}
}
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);
}
}
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);
}
}
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);
}
...
}
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();
}
public int Update(TModelDTO entityDto)
{
var entity = Map.For(entityDto);
return Repository.Update(entity);
}
entity update
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.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap1.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, Func3 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
Upvotes: 2
Views: 2965
Reputation: 21
Since I use automapper, my problem was solved using AutoMapper.Collection
my resolve issue
//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 ();
//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;
}
}
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);
}
p.s the repository referred to for the example did not update
Upvotes: 0
Reputation: 239290
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 (i.e. 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.
Upvotes: 3