使用xUnit编写单元测试时遇「实体类型实例无法被跟踪」错误
解决EF Core单元测试中的实体跟踪冲突错误
错误原因
测试中复用了同一个ApplicationDbContext实例:先通过它添加测试数据并执行SaveChanges,此时这些Book实体仍被上下文的跟踪器记录。当调用Update方法时,传入的newBook与上下文已跟踪的某一Book实体主键重复,此时尝试将newBook标记为修改状态,EF Core会因同一主键的实体已被跟踪而抛出"The instance of entity type cannot be tracked.."错误。
注意:生产环境中通常每个请求对应一个独立的DbContext实例,因此不会出现该冲突,但单元测试中复用实例就会触发问题。
解决方法
方法1:添加数据后解除实体跟踪
在测试的Arrange阶段,执行SaveChanges后手动解除所有实体的跟踪状态,让上下文不再持有这些实体的跟踪信息:
[Fact] public void UpdateBook() { /* Arrange */ var newBook = BookMockData.UpdateBook(); _context.Books.AddRange(BookMockData.GetBooks()); _context.SaveChanges(); // 解除所有实体的跟踪,避免后续Update时冲突 foreach (var entry in _context.ChangeTracker.Entries()) { entry.State = EntityState.Detached; } var sut = new BookRepository(_context); /* Assert & Act */ FluentActions.Invoking(() => sut.Update(newBook)).Should().NotThrow(); }
方法2:使用独立DbContext实例执行Update
模拟生产环境的上下文隔离,用一个DbContext添加测试数据,再创建新的DbContext实例用于仓库的Update操作:
[Fact] public void UpdateBook() { /* Arrange */ var newBook = BookMockData.UpdateBook(); var options = _context.Options; // 获取上下文配置 // 用独立上下文添加测试数据 using (var addContext = new ApplicationDbContext(options)) { addContext.Books.AddRange(BookMockData.GetBooks()); addContext.SaveChanges(); } // 新上下文用于执行Update操作 var sutContext = new ApplicationDbContext(options); var sut = new BookRepository(sutContext); /* Assert & Act */ FluentActions.Invoking(() => sut.Update(newBook)).Should().NotThrow(); }
方法3:调整仓库Update逻辑(可选)
修改仓库的Update方法,避免直接使用传入的实体,而是将属性值映射到查询到的实体上,再执行保存:
public override void Update(Book entity) { var result = _context.Books.AsNoTracking().SingleOrDefault(x => x.ProductId == entity.ProductId); if (result == null) throw new InvalidOperationException("Güncellemek istediğiniz kitap bulunamadı."); // 将传入实体的属性值映射到查询到的实体 _context.Entry(result).CurrentValues.SetValues(entity); base.Save(); }
此方法无需修改测试代码,同时让Update逻辑更健壮,避免依赖传入实体的跟踪状态。
内容的提问来源于stack exchange,提问作者Oguz Akcay
相关产品推荐
相关产品推荐

