MovieStoreApi POST请求报错:Director实体无法被上下文跟踪
问题:POST操作时出现Director实体跟踪冲突错误
执行MovieStoreApi的POST操作时,报错:实体类型Director的实例无法被跟踪,因为已有具有相同键值的实例被上下文跟踪
相关代码
Movie模型
public class Movie { public int Id { get; set; } public string MovieName { get; set; } public int PublishYear { get; set; } public string MovieGenre { get; set; } public Director Director { get; set; } public List<Performer> Performers { get; set; } public double Price { get; set; } }
Director模型
public class Director { public int Id { get; set; } public string Name { get; set; } public string Surname { get; set; } public List<Movie> DirectedMovies { get; set; } }
IRepository接口
public interface IRepository<TEntity> where TEntity : class { IEnumerable<TEntity> GetAll(); Task<TEntity> GetById(int id); void Create(TEntity entity); Task<TEntity> Update(TEntity entity); Task Delete(int id); }
Repository实现
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class { private readonly ApplicationDbContext _context; internal DbSet<TEntity> _dbSet; public Repository(ApplicationDbContext context) { _context = context; _dbSet = _context.Set<TEntity>(); } public void Create(TEntity entity) { _context.Set<TEntity>().Add(entity); _context.SaveChanges(); } public IEnumerable<TEntity> GetAll() { return _context.Set<TEntity>().AsNoTracking(); } }
IMovieRepository与MovieRepository
public interface IMovieRepository : IRepository<Movie> { } public class MovieRepository : Repository<Movie>, IMovieRepository { private ApplicationDbContext _context; public MovieRepository(ApplicationDbContext context) : base(context) { _context = context; } }
MovieController
public class MovieController : ControllerBase { private readonly IMovieRepository _movieRepository; public MovieController(IMovieRepository movie) { _movieRepository = movie; } [HttpGet] public async Task<IActionResult> GetMovies() { var allMovies = _movieRepository.GetAll(); return Ok(allMovies); } [HttpPost] public void PostMovie(Movie movie) { _movieRepository.Create(movie); } }
POST请求JSON体
包含MovieName、PublishYear、MovieGenre、Director(含Id、Name、Surname)、Performers、Price的JSON数据
错误原因分析
- 当POST的Movie对象包含已存在的Director(携带Id)时,EF Core执行
Add(entity)会递归处理所有关联实体,尝试将传入的Director实例标记为新增状态。但此时如果上下文已经跟踪了同一个Id的Director实例(比如之前的查询操作加载过该Director,或者同一请求内其他操作触发了加载),就会出现跟踪冲突。 - 即使
GetAll()用了AsNoTracking(),但如果传入的Director的Id对应数据库中已存在的记录,EF Core在处理关联时会尝试跟踪这个传入的实例,而如果上下文已经有该Id的跟踪实例,就会报错。 - 本质是你想关联已存在的Director,但EF Core默认将传入的关联实体视为新实体,尝试添加,导致与已跟踪的实例冲突。
解决方案
方法一:手动关联已存在的Director
在Create方法中,先将传入的已存在Director附加到上下文,标记为未修改状态,避免EF Core尝试新增:
public void Create(Movie entity) { // 处理已存在的Director if (entity.Director != null && entity.Director.Id != 0) { _context.Attach(entity.Director); // 或者查询数据库获取已跟踪的实例赋值 // entity.Director = _context.Directors.Find(entity.Director.Id); } // 处理Performers同理,如果是已存在的演员也需要附加或查询 if (entity.Performers != null) { foreach (var performer in entity.Performers.Where(p => p.Id != 0)) { _context.Attach(performer); } } _context.Set<TEntity>().Add(entity); _context.SaveChanges(); }
方法二:使用DTO(推荐)
不要直接接收实体类作为POST参数,定义专门的DTO来传输数据,只传递关联实体的Id而非整个对象:
// 定义MovieCreateDTO public class MovieCreateDTO { public string MovieName { get; set; } public int PublishYear { get; set; } public string MovieGenre { get; set; } public int DirectorId { get; set; } public List<int> PerformerIds { get; set; } public double Price { get; set; } }
修改Controller的Post方法,将DTO转换为实体并关联已存在的Director和Performers:
[HttpPost] public void PostMovie(MovieCreateDTO movieDto) { var movie = new Movie { MovieName = movieDto.MovieName, PublishYear = movieDto.PublishYear, MovieGenre = movieDto.MovieGenre, Director = _context.Directors.Find(movieDto.DirectorId), Performers = _context.Performers.Where(p => movieDto.PerformerIds.Contains(p.Id)).ToList(), Price = movieDto.Price }; _movieRepository.Create(movie); }
方法三:标记关联实体为未修改状态
如果确定传入的Director是已存在的,可以直接将其状态设置为Unchanged,避免EF Core尝试新增:
public void Create(Movie entity) { if (entity.Director != null && entity.Director.Id != 0) { _context.Entry(entity.Director).State = EntityState.Unchanged; } _context.Set<TEntity>().Add(entity); _context.SaveChanges(); }
内容的提问来源于stack exchange,提问作者Oguz Akcay
相关产品推荐
相关产品推荐

