我正在测试一项使用 xUnit 和内存数据库更新某些内容的服务,但在运行时收到此错误 UpdateContractPriceListGoodsItem_WhenEverythingIsOk_ShouldBeSucceeded 和 这条线想要执行
await _contractPriceListGoodsItemRepository.Update(contractPriceListGoodsItem);
实体类型“ContractPriceListGoodsItem”的实例不能是 被跟踪是因为另一个具有键值“{Id: 2}”的实例是 已经被追踪了。附加现有实体时,请确保 仅附加一个具有给定键值的实体实例。
在更新期间,有人可以解释其背后的原因以及如何解决此问题吗?
这是我的 ContractServiceTests
public class ContractServiceTests
{
private readonly IContractRepository _contractRepository;
private readonly IPriceListOperationItemRepository _priceListOperationItemRepository;
private readonly IPriceListGoodsItemRepository _priceListGoodsItemRepository;
private readonly IContractPriceListGoodsItemRepository _contractPriceListGoodsItemRepository;
private readonly IContractPriceListOperationItemRepository _contractPriceListOperationItemRepository;
private readonly IServiceOperationRepository _serviceOperationRepository;
private readonly IFeatureGoodsRepository _featureGoodsRepository;
private readonly IVisitProgramContractPriceListItemRepository _visitProgramContractPriceListItemRepository;
private readonly IVisitProgramPriceListItemRepository _visitProgramPriceListItemRepository;
private readonly IVisitRepository _visitRepository;
private readonly PMOracleDbContext _dbContextOptions;
private readonly IMapper _mapper;
public ContractServiceTests()
{
_dbContextOptions = new DbContextMocker().SetDbContext();
_contractRepository = new ContractRepository(_dbContextOptions);
_priceListOperationItemRepository = new PriceListOperationItemRepository(_dbContextOptions);
_priceListGoodsItemRepository = new PriceListGoodsItemRepository(_dbContextOptions);
_contractPriceListGoodsItemRepository = new ContractPriceListGoodsItemRepository(_dbContextOptions);
_contractPriceListOperationItemRepository = new ContractPriceListOperationItemRepository(_dbContextOptions);
_serviceOperationRepository = new ServiceOperationRepository(_dbContextOptions);
_featureGoodsRepository = new FeatureGoodsRepository(_dbContextOptions);
_visitProgramContractPriceListItemRepository = new VisitProgramContractPriceListItemRepository(_dbContextOptions);
_visitProgramPriceListItemRepository = new VisitProgramPriceListItemRepository(_dbContextOptions);
_visitRepository = new VisitRepository(_dbContextOptions);
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new ContractAutoMapperConfiguration());
});
_mapper = mappingConfig.CreateMapper();
}
[Theory]
[InlineData(2,35000)]
public async Task UpdateContractPriceListGoodsItem_WhenEverythingIsOk_ShouldBeSucceeded(long contractPriceListGoodsItemId,long price)
{
// Arrange
var _contractService = new ContractService(
_contractRepository ,
_mapper,
_priceListOperationItemRepository,
_priceListGoodsItemRepository,
_contractPriceListGoodsItemRepository,
_contractPriceListOperationItemRepository,
_serviceOperationRepository,
_featureGoodsRepository,
_visitProgramContractPriceListItemRepository,
_visitProgramPriceListItemRepository,
_visitRepository);
// Act
await _contractService.UpdateContractPriceListGoodsItem(contractPriceListGoodsItemId, price);
// Assert
var result = _contractPriceListGoodsItemRepository.GetAll().SingleOrDefault(a => a.Id == contractPriceListGoodsItemId);
result.GoodsPrice.ShouldBeEquivalentTo(price);
}
}
这是我的服务
public async Task<IdDto> UpdateContractPriceListGoodsItem(long contractPriceListGoodsItemId, decimal price)
{
var contractPriceListGoodsItem = await _contractPriceListGoodsItemRepository
.GetContractPriceListGoodsItemById(contractPriceListGoodsItemId);
if (contractPriceListGoodsItem is null)
throw new NotFoundException(Resources.Resources.ContractPriceListItemNotFoundException);
contractPriceListGoodsItem.GoodsPrice = price;
await _contractPriceListGoodsItemRepository.Update(contractPriceListGoodsItem);
// Response
return new IdDto { Id = contractPriceListGoodsItem.Id };
}
这是我的 DbContextMocker
public class DbContextMocker : IDisposable
{
protected readonly PMOracleDbContext _dbContext;
public DbContextMocker()
{
var options = new DbContextOptionsBuilder<PMOracleDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.EnableSensitiveDataLogging()
.Options;
_dbContext = new PMOracleDbContext(options);
_dbContext.Database.EnsureCreated();
}
public void Dispose()
{
_dbContext.Database.EnsureDeleted();
_dbContext.Dispose();
}
public PMOracleDbContext SetDbContext() { SeedDatabase.Initialize(_dbContext);
return _dbContext;
}
}
这是我的更新代码
public async Task Update(TEntity entity,CancellationToken cancellationToken = default(CancellationToken))
{
DbContext.Update(entity);
await DbContext.SaveChangesAsync(cancellationToken);
}
这是我的种子
public class SeedDatabase
{
public static void Initialize(PMOracleDbContext context)
{
Seed(context);
}
private static void Seed(PMOracleDbContext context)
{
context.Contracts.AddRange(FakeListContract());
context.SaveChanges();
}
private static IEnumerable<Contract> FakeListContract()
{
return new List<Contract>
{
new Contract
{
Id = 1,
Number = "20000",
Title = "First Test",
Price = 10000,
StartDate = DateTime.Now.AddDays(-10),
EndDate = DateTime.Now.AddDays(10),
IsActive = true,
ContractorId = 1,
ProgramType = ProgramType.ServiceProgram,
Description = "Test Description 1"
},
new Contract
{
Id = 2,
Number = "21000",
Title = "Second Test",
Price = 20000,
StartDate = DateTime.Now.AddDays(-15),
EndDate = DateTime.Now.AddDays(15),
IsActive = true,
ContractorId = 1,
ProgramType = ProgramType.VisitProgram,
Description = "Test Description 2"
}
};
}
}
此错误是由于首先通过
GetContractPriceListGoodsItemById()
缓存实体,然后通过 Update()
重新附加实体而引起的。如果您想这样做,您需要首先在存储库的第一个方法中提取实体。
但是,这并不是 EF core 应该工作的方式。默认情况下,您的对象由更改跟踪器跟踪。通过更改属性,实体会自动标记为脏,您只需调用
SaveChanges
或 SaveChangesAsync
即可将更改持久保存到数据库中。这比分离实体更有效,因为更改跟踪器将检测哪些属性未受影响并且不需要在 UPDATE 语句中发送到数据库。
另请注意,EF core InMemory 旨在测试 EF core,而不是应用程序。您应该模拟您的存储库或使用实际的数据库驱动程序,而不是使用 InMemory。