一、问题
新项目是基于 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()
方法,手动跟踪导航属性,也能够达到上述效果。
四、参考资料
StackOverflow - Entity Framework disconnected graph and navigation property
MSDN - Disconnected entities