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

一、问题

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

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

1203160-20190918105052304-169112939.gif

二、原因

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

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

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

三、解决

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

private readonly IUnitOfWorkManager _uowMgr;
private readonly IRepository<TestUser, Guid> _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("琴台路");
}

1203160-20190918105104278-609245608.png

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

public class TestUserRepository : EfCoreRepository<XXXDbContext,TestUser,Guid>
{
    public TestUserRepository(IDbContextProvider<XXXDbContext> dbContextProvider) : base(dbContextProvider)
    {
    }

    public override IQueryable<TestUser> WithDetails()
    {
        return GetQueryable().Include(x => x.Address);
    }

    public override Task<TestUser> UpdateAsync(TestUser entity, bool autoSave = false, CancellationToken cancellationToken = new CancellationToken())
    {
        DbContext.Attach(entity.Address).State = EntityState.Modified;
        return base.UpdateAsync(entity, autoSave, cancellationToken);
    }
}

1203160-20190918105113939-419595766.png

四、参考资料

转载于:https://www.cnblogs.com/myzony/p/11540330.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值