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



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

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

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

控制器

[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请求来修改随机团队建设中的一些字段

  • 我得到上面显示的错误

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

但是,根据我的代码,请求不应该创建一个新的上下文吗?然后,在请求完成并向客户端提供响应后,将要处理的上下文,对于新的请求,创建一个没有前一个信息的全新上下文吗?正如我现在看到的,我用GET请求创建了一个上下文,然后PUT请求重用该上下文,因此出现了"无法跟踪"错误。

我做错了什么?如果一切都好的话,在Id之后获得对象的方法是好的做法吗?

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

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核心的变化跟踪有关。

最新更新