在单元测试和内存数据库中无法跟踪实体类型的实例

问题描述 投票:0回答:1

我正在测试一项使用 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"
                }
            };
        }

    }
c# .net entity-framework testing xunit
1个回答
0
投票

此错误是由于首先通过

GetContractPriceListGoodsItemById()
缓存实体,然后通过
Update()
重新附加实体而引起的。如果您想这样做,您需要首先在存储库的第一个方法中提取实体。

但是,这并不是 EF core 应该工作的方式。默认情况下,您的对象由更改跟踪器跟踪。通过更改属性,实体会自动标记为脏,您只需调用

SaveChanges
SaveChangesAsync
即可将更改持久保存到数据库中。这比分离实体更有效,因为更改跟踪器将检测哪些属性未受影响并且不需要在 UPDATE 语句中发送到数据库。

另请注意,EF core InMemory 旨在测试 EF core,而不是应用程序。您应该模拟您的存储库或使用实际的数据库驱动程序,而不是使用 InMemory。

详细信息:https://learn.microsoft.com/en-us/ef/core/testing/

© www.soinside.com 2019 - 2024. All rights reserved.