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

时间:2018-08-02 11:18:56

标签: c# asp.net-core autofac ef-core-2.0

我正在开发一个应管理团队建设的应用程序,并且我在后端使用.NET Core和EF Core,以及用于依赖项注入的Autofac。在我的页面中,当我从后端将所有团队建设放在列表中,然后尝试修改其中一个的值时,出现以下错误:

  

无法跟踪实体类型'TeamBuilding'的实例,因为已经跟踪了另一个具有相同键值的{'Id'}实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。考虑使用'DbContextOptionsBuilder.EnableSensitiveDataLogging'查看冲突的键值

这是我使用的类和方法:

控制器

[SerializeField]
private DeathUI deathUi;

// ...

Debug.Log("death");
deathUi.deathUI();

服务

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

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

  • 发出GET请求以获取所有团队建设

  • 在相同的浏览器,服务器实例上以及之后,我尝试通过发出PUT请求来修改随机团队建设中的某些字段

  • 我收到上面显示的错误

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

但是根据我的代码,请求不应该创建一个新的上下文,然后在完成请求并将响应提供给客户端之后,该上下文将被处置;对于新请求,一个全新的上下文没有有关上一个的信息被创建吗?就像我现在看到的那样,我使用GET请求创建一个上下文,然后该上下文被PUT请求重用,因此出现“无法跟踪”错误。

我在做什么错了,如果实际上一切正常,那么在Id之后获取对象的方法是一种好习惯吗?

2 个答案:

答案 0 :(得分:1)

编辑:我刚刚注意到您的GetById方法返回了一个viewmodel。您必须像这样操纵实体

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

这是这行

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

这将创建对象Teambuilding的新实例。您需要像在控制器中一样加载现有的(无论如何都不应在其中完成)。在您的服务级别上这样做:

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

现在,由dbcontext跟踪的对象是相同的,并且更新可以正常工作。现在,您将尝试在数据库中创建新行。这与ef-core的更改跟踪有关。

答案 1 :(得分:-1)

问题是代码中的以下行基于问题描述的倒数第二段:

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

InstancePerLifetimeScope本质上使上下文成为Singleton。