I am using .Net Core 3.1 app with aspnet boiler plate template. ABP provides IUnitOfWorkManager. We can either inject IUnitOfWorkManager in the constructor e.g
private readonly IUnitOfWorkManager _unitOfWorkManager;
public MyClass(IUnitOfWorkManager unitOfWorkManager
{
_unitOfWorkManager = unitOfWorkManager
}
public async Task MyMethod()
{
var context = _unitOfWorkManager.Current.GetDbContext<MyDbContext>();
...
}
Or we can direcrly ue the UnitOfWorkManager property provided by ABP in the method e.g:
public async Task MyMethod()
{
var context = UnitOfWorkManager.Current.GetDbContext<MyDbContext>();
...
}
public IUnitOfWorkManager UnitOfWorkManager
{
get
{
if (_unitOfWorkManager == null)
{
throw new AbpException("Must set UnitOfWorkManager before use it.");
}
return _unitOfWorkManager;
}
set
{
_unitOfWorkManager = value;
}
}
The question is, do I need to call Dispose() method for disposing the DBContext if I use either of the above mentioned approach? Because I am getting pool starvation exceptions in the logs and one of the most called Api is using the second approach.
For the ABP UnitOfWorkManager, from the documentation and naming convention I would say that No, you would not want to explicitly dispose of the DbContext
returned by "GetDbContext()". From the limited example code I could find around the ABP framework, the examples do not wrap the DbContext
instances retrieved with using()
blocks so I fully expect that it is operating as a service locator, exposing the instance it is using within its UoW/Repository wrapper classes. However, that is without looking at the source code (if available) for the ABP framework. It comes down to whether the manager is acting as a Service Locator or a Factory. A factory pattern would create and return a new instance of a DbContext
, and this typically, but not always implies that the consumer should be responsible for disposal. (At the very least the factory should expect that the consumer may, or may not dispose of the instance) A Service Locator pattern on the other hand will manage the lifetime scope of the items it holds and provide references to those instances. In these cases you do not want to explicitly dispose of an instance, as that instance is shared between all code requesting that reference.
There is one simple debugging test you can try to determine if the GetDbContext is a factory or service locator method:
var context1 = UnitOfWorkManager.Current.GetDbContext<MyDbContext>();
var context2 = UnitOfWorkManager.Current.GetDbContext<MyDbContext>();
bool isSameReference = Object.ReferenceEquals(context1, context2);
If "isSameReference" comes back as true
then you should not dispose of the instance. If it is false
and the Get method is returning new instances then you most likely should dispose.
Edit: For ABP and it's UnitOfWorkManager, if you have parallel tasks you should be able to leverage the .Begin()
method to scope a DbContext for the task. To verify this you can debug the following test code and it should result in different references. You won't need to explicitly dispose of the DbContext in this case, but wrap the UnitOfWork returned by the manager with a using()
block to dispose it and the contained DbContext:
using (var uow1 = UnitOfWorkManager.Begin())
{
var context1 = uow1.GetDbContext<MyDbContext>();
using (var uow2 = UnitOfWorkManager.Begin())
{
var context2 = uow2.GetDbContext<MyDbContext>();
bool isSameReference = Object.ReferenceEquals(context1, context2);
} // uow2 and context2 will be disposed...
} // uow1 and context1 will be disposed...
I would fully expect that "isSameReference" would be false in the above case.
If so, then the Task method to be parallelized can be updated to:
public async Task MyMethod()
{
using (uow = _unitOfWorkManager.Begin())
{
var context = uow.GetDbContext<MyDbContext>();
...
}
}
This would allow parallelization and ensure the DbContext instance is disposed.