EF Core:无法跟踪实体类型的实例和上下文依赖注入

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

我正在开发一个应该管理团队建设的应用程序,我在后端使用 .NET Core 和 EF Core,并使用 Autofac 进行依赖注入。在我的页面中,在我从后端的列表中获取所有团队建设后,我尝试修改其中一个的值,我收到以下错误:

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

以下是我使用的 类 和方法:

控制器

    [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);
            }
        }
    }

服务

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;
        }
    }
}

存储库

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();
        }
    }

依赖注入部分

        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);

为了更准确地详细说明问题,我做了以下事情:

我知道解决方案之一是先从数据库中获取我要更新的对象,然后在该对象上用新值修改其字段,然后将其传递给更新函数。

但是根据我的代码,请求不应该创建一个新的上下文,然后在请求完成并且响应被提供给客户端之后,要处理的上下文,并且对于一个新的请求一个全新的没有关于前一个信息的上下文被创建?正如我现在所看到的,我使用 GET 请求创建了一个上下文,然后 PUT 请求重用了该上下文,因此出现了 "Cannot be tracked" 错误。

我哪里做错了,如果一切正常,那么先在Id之后获取对象的方法是好的做法吗?

编辑: 我刚刚注意到您的 GetById 方法 returns 一个视图模型。你必须像那样操纵实体

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

这里就是这一行

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

这将创建对象 Teambuilding 的新实例。您需要像在控制器中那样加载现有的控制器(无论如何都不应该在那里完成)。像你的服务那样做-class:

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

现在 dbcontext 跟踪的对象是相同的,更新将正常工作。你现在这样做的方式会尝试在数据库中创建一个新行。这个跟ef-core的change-tracking有关。