在 ABP vNext 中编写仓储单元测试的问题一则

一、问题

新项目是基于 ABP vNext 框架进行开发的,所以我要求为每层编写单元测试。在同事为某个仓储编写单元测试的时候,发现了一个奇怪的问题。他的对某个聚合根的 A 字段进行了更新,随后对某个导航属性 B 也进行了变更,最后通过仓储提供的 UpdateAsync() 方法对变更的数据进行持久化。

结果再次查出来的时候,发现聚合根的 A 字段倒是更新了,但是导航属性 B 的内部字段没有进行变更。例如在下面的实例当中,聚合根的 Name 字段变更成功,但是导航属性的 Street 字段变更失败了。

在 ABP vNext 中编写仓储单元测试的问题一则_第1张图片

二、原因

数据没有更新到,说明问题肯定出在 UpdateAsync 方法内部,通过打断点单步步入之后,也没发现有什么奇怪的地方,是使用的 ABP vNext 提供的默认仓储实现。

又在想是否跟实体追踪有关,然后看同事写得单元测试代码,发现他是先使用的 GetAsync() 方法获取到实体,然后手动变更了实体的属性。变更完成之后,通过仓储提供的 UpdateAsync() 方法进行更新。

看了很久发现它们并不是公用的一个工作单元,这就导致 GetAsync() 和 UpdateAsync() 方法内部得到的 DbContext 是不一样的。在 EF Core 内部针对这种情况,称之为 Disconnected entities 即断开连接的实体,这个时候需要用户手动 Attch 追踪导航属性。

三、解决

所以有两种解决办法,第一种方法是保证使用 GetAsync() 和 UpdateAsync() 方法时,它们都处于一个工作单元下,例如下面的伪代码。

private readonly IUnitOfWorkManager _uowMgr;
private readonly IRepository _repository;

[Fact]
public async Task Resolve1()
{
    
    var entityId = Guid.NewGuid();
    await _repository.InsertAsync(new TestUser
    {
        Id = entityId,
        Name = "张三",
        Address = new TestUserAddress
        {
            City = "成都市",
            Street = "春熙路"
        }
    });

    using (var outerUow = _uowMgr.Begin())
    {
        var entity = await _repository.GetAsync(entityId);
        entity.Name = "李四";
        entity.Address.Street = "琴台路";

        await _repository.UpdateAsync(entity);
        await outerUow.CompleteAsync();
    }
    
    
    var result = await _repository.GetAsync(entityId);
    result.Name.ShouldBe("李四");
    result.Address.Street.ShouldBe("琴台路");
}

在 ABP vNext 中编写仓储单元测试的问题一则_第2张图片

第二种方法变动则要大一些, 导航属性没有更新的根本原因,是因为在第二个工作单元中没有追踪到这个属性,你只需要手动附加该导航属性即可。在下面的例子中,我们重写了 UpdateAsync() 方法,手动跟踪导航属性,也能够达到上述效果。


在 ABP vNext 中编写仓储单元测试的问题一则_第3张图片

四、参考资料

  • StackOverflow - Entity Framework disconnected graph and navigation property

  • MSDN - Disconnected entities

你可能感兴趣的:(在 ABP vNext 中编写仓储单元测试的问题一则)