可以看到,仓储使用到的数据库上下文对象是通过工作单元的 IServiceProvider 进行解析的。回想之前关于工作单元的文章讲解,不论是手动开启工作单元,还是通过拦截器或者特性的方式开启,最终都是使用的 IUnitOfWorkManager.Begin() 进行构建的。
public class UnitOfWorkManager : IUnitOfWorkManager, ISingletonDependency { // ... 省略的不相关的代码。 private readonly IHybridServiceScopeFactory _serviceScopeFactory; // ... 省略的不相关的代码。 public IUnitOfWork Begin(UnitOfWorkOptions options, bool requiresNew = false) { // ... 省略的不相关的代码。 var unitOfWork = CreateNewUnitOfWork(); // ... 省略的不相关的代码。 return unitOfWork; } // ... 省略的不相关的代码。 private IUnitOfWork CreateNewUnitOfWork() { var scope = _serviceScopeFactory.CreateScope(); try { // ... 省略的不相关的代码。 // 所以 IUnitOfWork 里面获得的 ServiceProvider 是一个子容器。 var unitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork>(); // ... 省略的不相关的代码。 // 工作单元被释放的动作。 unitOfWork.Disposed += (sender, args) => { _ambientUnitOfWork.SetUnitOfWork(outerUow); // 子容器被释放时,通过子容器解析的 DbContext 也被释放了。 scope.Dispose(); }; return unitOfWork; } catch { scope.Dispose(); throw; } } }工作单元的 ServiceProvider 是通过继承 IServiceProviderAccessor 得到的,也就是说在构建工作单元的时候,这个 Provider 就是工作单元管理器创建的子容器。
那么回到之前的代码,我们得知 DbContext 是通过工作单元的 ServiceProvider 创建的,当工作单元被释放的时候,也会连带这个子容器被释放。那么我们之前解析出来的 DbContext ,也就会随着子容器的释放而被释放。如果要验证上述猜想,只需要编写类似代码即可。
[Fact] public void TestMethod() { using (var scope = GetRequiredService<IServiceProvider>().CreateScope()) { var dbContext = scope.ServiceProvider.GetRequiredService<IHospitalDbContext>(); scope.Dispose(); } }既然如此,工作单元是什么时候被释放的呢...因为拦截器默认是为仓储建立了拦截器,所以在获得到 DbContext 的时候,拦截器已经将之前的 DbContext 释放掉了。
public override void Intercept(IAbpMethodInvocation invocation) { if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method, out var unitOfWorkAttribute)) { invocation.Proceed(); return; } // 我在这里... using (var uow = _unitOfWorkManager.Begin(CreateOptions(invocation, unitOfWorkAttribute))) { invocation.Proceed(); uow.Complete(); } }要验证 DbContext 是随工作单元一起释放,也十分简单,编写以下代码即可进行测试。
[Fact] public void TestMethod() { var rep = GetRequiredService<IHospitalRepository>(); var mgr = GetRequiredService<IUnitOfWorkManager>(); using (var uow = mgr.Begin()) { var count = rep.Count(); uow.Dispose(); uow.Complete(); } } 三、解决解决方法很简单,在有类似操作的外部通过 [UnitOfWork] 特性或者 IUnitOfManager.Begin 开启一个新的工作单元即可。
[Fact] public void TestMethod() { var rep = GetRequiredService<IHospitalRepository>(); var mgr = GetRequiredService<IUnitOfWorkManager>(); using (var uow = mgr.Begin()) { var count = rep.Count(); uow.Complete(); } }