简体   繁体   中英

EF Core: The instance of entity type cannot be tracked and context dependency injection

I am developing an app that should manage teambuildings, and I am using .NET Core and EF Core for my backend, together with Autofac for dependency injection. In my page, after I get all my teambuildings in a list from the backend, and then I try to modify the values for one of them, I get the following error:

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

Here are the classes and methods I use:

Controller

    [Produces("application/json")]
    [Route("api/teamBuildings")]
    public class TeamBuildingController : Controller
    {
        public ITeamBuildingService _service;

        public TeamBuildingController(ITeamBuildingService serviceTeam)
        {
            _service = serviceTeam;
        }

        [HttpPost]
        public IActionResult Create([FromBody]TeamBuildingForCreationDto teamBuilding)
        {
            try
            {
                var existingTb = _service.GetByID(teamBuilding.Id);
                if (existingTb != null)
                {
                    return BadRequest("An entry with this id already exists");
                }

                _service.Create(teamBuilding);
                return Ok();
            }
            catch (Exception ex)
            {
                return BadRequest(ex.Message);
            }
        }

        [HttpGet]
        public IActionResult GetAll()
        {
            var teamBuildings = _service.GetAll();
            if (teamBuildings == null)
            {
                return NotFound("There are no team buidings");
            }
            return Ok(teamBuildings);
        }

        [HttpGet("{id}")]
        public IActionResult GetTeambuilding(int id)
        {
            var teamBuilding = _service.GetByID(id);
            if (teamBuilding == null)
            {
                return NotFound("There is no team buiding with such an ID");
            }
            return Ok(teamBuilding);
        }

        [HttpPut]
        public IActionResult UpdateTeamBuilding([FromBody]TeamBuildingViewModel viewModel)
        {
            try
            {
                var existingTeamBuilding = _service.GetByID(viewModel.Id);
                if (existingTeamBuilding == null)
                {
                    return NotFound("There is no team buiding with such an ID");
                }

                _service.UpdateTeamBuilding(viewModel);
                return Ok();
            }
            catch (Exception ex)
            {
                return BadRequest(ex.Message);
            }
        }
    }

Service

public class TeamBuildingService : ITeamBuildingService
    {
        private IGenericRepository<DAL.Models.TeamBuilding> _repositoryTeam;

        public TeamBuildingService(IGenericRepository<DAL.Models.TeamBuilding> repositoryTeam)
        {
            _repositoryTeam = repositoryTeam;
        }

        public TeamBuildingDetailsViewModel GetByID(int id)
        {
            var teamBuilding = _repositoryTeam.GetByID(id);
            var viewModel = Mapper.Map<TeamBuildingDetailsViewModel>(teamBuilding);
            return viewModel;
        }

        public IEnumerable<TeamBuildingViewModel> GetAll()
        {
              //code which returns all the teambuilding from the database, omitted on purpose
        }


        public TeamBuildingViewModel UpdateTeamBuilding(TeamBuildingViewModel teamBuildingViewModel)
        {
            var teamBuilding = Mapper.Map<DAL.Models.TeamBuilding>(teamBuildingViewModel);

            _repositoryTeam.Edit(teamBuilding);
            _repositoryTeam.Commit();
            return teamBuildingViewModel;
        }
    }
}

Repository

public class GenericRepository<T> : IGenericRepository<T> where T : class
    {
        public DbContext _context;
        public DbSet<T> dbset;

        public GenericRepository(DbContext context)
        {
            _context = context;
            dbset = context.Set<T>();
        }

        public IQueryable<T> GetAll()
        {
            return dbset;
        }

        public T GetByID(params object[] keyValues)
        {
            return dbset.Find(keyValues);
        }

        public void Edit(T entity)
        {
            _context.Entry(entity).State = EntityState.Modified;
        }

        public void Insert(T entity)
        {
            dbset.Add(entity);
        }

        public void Delete(T entity)
        {
            _context.Entry(entity).State = EntityState.Deleted;
        }

        public T GetByFunc(Func<T, bool> func)
        {
            return dbset.AsQueryable().Where(x => func(x)).FirstOrDefault();
        }

        public void Commit()
        {
            _context.SaveChanges();
        }
    }

The Dependency Injection part

        var builder = new ContainerBuilder();
        builder.Populate(services);

        builder.RegisterType<UserController>();
        builder.RegisterType<TeamBuildingController>();
        builder.RegisterType<UserService>().As<IUserService>();
        builder.RegisterType<TeamBuildingService>().As<ITeamBuildingService>();
        builder.RegisterType<TeamBuildingContext>().As<DbContext>().InstancePerLifetimeScope();

        builder.RegisterGeneric(typeof(GenericRepository<>))
            .As(typeof(IGenericRepository<>));

        this.ApplicationContainer = builder.Build();

        // Create the IServiceProvider based on the container.
        return new AutofacServiceProvider(this.ApplicationContainer);

To detail the problem more exactly, I do the following things:

  • make a GET request to get all the teambuildings

  • from the same browser, server instance and immediately after, I try to modify some of the fields on a random teambuilding by making a PUT request

  • I get the error shown above

I know one of the solutions is to get the object I want to update from the database first , then on that object to modify its fields with the new values, then pass it to the update function.

But shouldn't the request, according to my code, create a new context, then after the request is done and the response was given to the client the context to be disposed, and for a new request a completely new context that has no information about the previous one be created? As I see it now, I create a context with the GET request, then the context is reused by the PUT request, hence the "Cannot be tracked" error.

What am I doing wrong, and if everything is actually ok, is the method of getting the object after the Id first the good practice?

Edit: I just noticed your GetById method returns a viewmodel. You must manipulate the entity like that

var teamBuilding = _repositoryTeam.GetByID(id);
Mapper.Map(teamBuildingViewModel, teamBuilding);
_repositoryTeam.Edit(teamBuilding);
_repositoryTeam.Commit();

It is this line here

var teamBuilding = Mapper.Map<DAL.Models.TeamBuilding>(teamBuildingViewModel);

This creates a new instance of the object Teambuilding. You need to load the existing one as you do in the controller (which should not be done there anyway). Do it like that from your service-class:

var teamBuilding = this.GetByID(viewModel.Id);
Mapper.Map(teamBuildingViewModel, teamBuilding);
_repositoryTeam.Edit(teamBuilding);
_repositoryTeam.Commit();

Now the object that is being tracked by the dbcontext is the same and update will work just fine. The way you are doing it now it would try to create a new row in the database. This is related to the change-tracking of ef-core.

The problem is the following line in your code based on the second to last paragraph in your problem description:

builder.RegisterType<TeamBuildingContext>().As<DbContext>().InstancePerLifetimeScope();

The InstancePerLifetimeScope essentially makes the context a Singleton.

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