在 ABP vNext 中编写仓储单元测试的问题一则
- 2019 年 10 月 3 日
- 筆記
一、问题
新项目是基于 ABP vNext 框架进行开发的,所以我要求为每层编写单元测试。在同事为某个仓储编写单元测试的时候,发现了一个奇怪的问题。他的对某个聚合根的 A 字段进行了更新,随后对某个导航属性 B 也进行了变更,最后通过仓储提供的 UpdateAsync()
方法对变更的数据进行持久化。
结果再次查出来的时候,发现聚合根的 A 字段倒是更新了,但是导航属性 B 的内部字段没有进行变更。例如在下面的实例当中,聚合根的 Name
字段变更成功,但是导航属性的 Street
字段变更失败了。
二、原因
数据没有更新到,说明问题肯定出在 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("琴台路"); }
第二种方法变动则要大一些, 导航属性没有更新的根本原因,是因为在第二个工作单元中没有追踪到这个属性,你只需要手动附加该导航属性即可。在下面的例子中,我们重写了 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); } }
四、参考资料
- StackOverflow – Entity Framework disconnected graph and navigation property
- MSDN – Disconnected entities