一、問題
新項目是基于 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