如何在 ABP 应用程序的 BackgroundJob 中打开数据库连接
How to open database connection in a BackgroundJob in ABP application
问题
为了测试,我创建了一个新作业,它只是使用 IRepository 从数据库中读取数据。代码如下:
public class TestJob : BackgroundJob<string>, ITransientDependency
{
private readonly IRepository<Product, long> _productRepository;
private readonly IUnitOfWorkManager _unitOfWorkManager;
public TestJob(IRepository<Product, long> productRepository,
IUnitOfWorkManager unitOfWorkManager)
{
_productRepository = productRepository;
_unitOfWorkManager = unitOfWorkManager;
}
public override void Execute(string args)
{
var task = _productRepository.GetAll().ToListAsync();
var items = task.Result;
Debug.WriteLine("test db connection");
}
}
然后我创建一个新的应用程序服务来触发作业。代码片段如下:
public async Task UowInJobTest()
{
await _backgroundJobManager.EnqueueAsync<TestJob, string>("aaaa");
}
当我测试作业时,它会在执行时抛出以下异常 var task = _productRepository.GetAll().ToListAsync();
Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.Object name: 'AbpExampleDbContext'.
解决方案
S1: 在执行方法上添加 UnitOfWork 属性。它可以解决这个问题。但这并不适合我的实际情况。在我的实际场景中,作业是一个长时间的任务,并且有很多DB操作,如果为Execute方法启用UnitOfWork,它会长时间锁定db资源。所以这不是我的方案的解决方案。
[UnitOfWork]
public override void Execute(string args)
{
var task = _productRepository.GetAll().ToListAsync();
var items = task.Result;
Debug.WriteLine("test db connection");
}
S2: 在 UnitOfWork 中显式执行 DB 操作。此外,这可以解决问题,但我认为这不是最佳做法。在我的例子中,只是从数据库中读取数据,不需要事务。尽管问题得到解决,但我认为这不是正确的方法。
public override void Execute(string args)
{
using (var unitOfWork = _unitOfWorkManager.Begin())
{
var task = _productRepository.GetAll().ToListAsync();
var items = task.Result;
unitOfWork.Complete();
}
Debug.WriteLine("test db connection");
}
问题
我的问题是在 BackgroundJob 中执行数据库操作的正确和最佳方法是什么?
还有一个问题,我创建了一个新的应用程序服务,并禁用了 UnitOfWrok,但它工作正常。请看下面的代码。为什么在application service中可以正常使用,在BackgroundJob中却不行?
[UnitOfWork(IsDisabled =true)]
public async Task<GetAllProductsOutput> GetAllProducts()
{
var result = await _productRepository.GetAllListAsync();
var itemDtos = ObjectMapper.Map<List<ProductDto>>(result);
return new GetAllProductsOutput()
{
Items = itemDtos
};
}
Background Jobs And Workers 上的文档使用 [UnitOfWork]
属性。
S1: Add UnitOfWork attribute on execute method. It can address the issue. But it is not better for my actual scenario. In my actual scenario, the job is a long time task, and has much DB operatons, if enable UnitOfWork for Execute method, it will lock db resource for a long time. So this is not a solution for my scenario.
后台作业 运行 在后台线程上同步进行,因此这种担忧是没有根据的。
S2: Execute DB operation in UnitOfWork explicitly. Also, this can address the issue, but I don’t think this is a best practice. In my example,just read data from database, no transaction is required. Even-though the issue is addressed, but I don’t think it’s a correct way.
您可以使用 Non-Transactional Unit Of Work:
[UnitOfWork(isTransactional: false)]
public override void Execute(string args)
{
var task = _productRepository.GetAll().ToListAsync();
var items = task.Result;
}
您可以使用 IUnitOfWorkManager
:
public override void Execute(string args)
{
using (var unitOfWork = _unitOfWorkManager.Begin(TransactionScopeOption.Suppress))
{
var task = _productRepository.GetAll().ToListAsync();
var items = task.Result;
unitOfWork.Complete();
}
}
你也可以使用AsyncHelper
:
[UnitOfWork(isTransactional: false)]
public override void Execute(string args)
{
var items = AsyncHelper.RunSync(() => _productRepository.GetAll().ToListAsync());
}
常规工作单元方法
I create a new application service, and disable UnitOfWork, but it works fine.
Why it works fine in application service, but doesn’t work in BackgroundJob?
[UnitOfWork(IsDisabled = true)]
public async Task<GetAllProductsOutput> GetAllProducts()
{
var result = await _productRepository.GetAllListAsync();
var itemDtos = ObjectMapper.Map<List<ProductDto>>(result);
return new GetAllProductsOutput
{
Items = itemDtos
};
}
您使用的方法不同:GetAllListAsync()
与 GetAll().ToListAsync()
存储库方法是 Conventional Unit Of Work Methods,但 ToListAsync()
不是。
来自 About IQueryable<T>
上的文档:
When you call GetAll()
outside of a repository method, there must be an open database connection. This is because of the deferred execution of IQueryable<T>
. It does not perform a database query unless you call the ToList()
method or use the IQueryable<T>
in a foreach
loop (or somehow access the queried items). So when you call the ToList()
method, the database connection must be alive.
问题
为了测试,我创建了一个新作业,它只是使用 IRepository 从数据库中读取数据。代码如下:
public class TestJob : BackgroundJob<string>, ITransientDependency
{
private readonly IRepository<Product, long> _productRepository;
private readonly IUnitOfWorkManager _unitOfWorkManager;
public TestJob(IRepository<Product, long> productRepository,
IUnitOfWorkManager unitOfWorkManager)
{
_productRepository = productRepository;
_unitOfWorkManager = unitOfWorkManager;
}
public override void Execute(string args)
{
var task = _productRepository.GetAll().ToListAsync();
var items = task.Result;
Debug.WriteLine("test db connection");
}
}
然后我创建一个新的应用程序服务来触发作业。代码片段如下:
public async Task UowInJobTest()
{
await _backgroundJobManager.EnqueueAsync<TestJob, string>("aaaa");
}
当我测试作业时,它会在执行时抛出以下异常 var task = _productRepository.GetAll().ToListAsync();
Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.Object name: 'AbpExampleDbContext'.
解决方案
S1: 在执行方法上添加 UnitOfWork 属性。它可以解决这个问题。但这并不适合我的实际情况。在我的实际场景中,作业是一个长时间的任务,并且有很多DB操作,如果为Execute方法启用UnitOfWork,它会长时间锁定db资源。所以这不是我的方案的解决方案。
[UnitOfWork]
public override void Execute(string args)
{
var task = _productRepository.GetAll().ToListAsync();
var items = task.Result;
Debug.WriteLine("test db connection");
}
S2: 在 UnitOfWork 中显式执行 DB 操作。此外,这可以解决问题,但我认为这不是最佳做法。在我的例子中,只是从数据库中读取数据,不需要事务。尽管问题得到解决,但我认为这不是正确的方法。
public override void Execute(string args)
{
using (var unitOfWork = _unitOfWorkManager.Begin())
{
var task = _productRepository.GetAll().ToListAsync();
var items = task.Result;
unitOfWork.Complete();
}
Debug.WriteLine("test db connection");
}
问题
我的问题是在 BackgroundJob 中执行数据库操作的正确和最佳方法是什么?
还有一个问题,我创建了一个新的应用程序服务,并禁用了 UnitOfWrok,但它工作正常。请看下面的代码。为什么在application service中可以正常使用,在BackgroundJob中却不行?
[UnitOfWork(IsDisabled =true)]
public async Task<GetAllProductsOutput> GetAllProducts()
{
var result = await _productRepository.GetAllListAsync();
var itemDtos = ObjectMapper.Map<List<ProductDto>>(result);
return new GetAllProductsOutput()
{
Items = itemDtos
};
}
Background Jobs And Workers 上的文档使用 [UnitOfWork]
属性。
S1: Add UnitOfWork attribute on execute method. It can address the issue. But it is not better for my actual scenario. In my actual scenario, the job is a long time task, and has much DB operatons, if enable UnitOfWork for Execute method, it will lock db resource for a long time. So this is not a solution for my scenario.
后台作业 运行 在后台线程上同步进行,因此这种担忧是没有根据的。
S2: Execute DB operation in UnitOfWork explicitly. Also, this can address the issue, but I don’t think this is a best practice. In my example,just read data from database, no transaction is required. Even-though the issue is addressed, but I don’t think it’s a correct way.
您可以使用 Non-Transactional Unit Of Work:
[UnitOfWork(isTransactional: false)]
public override void Execute(string args)
{
var task = _productRepository.GetAll().ToListAsync();
var items = task.Result;
}
您可以使用 IUnitOfWorkManager
:
public override void Execute(string args)
{
using (var unitOfWork = _unitOfWorkManager.Begin(TransactionScopeOption.Suppress))
{
var task = _productRepository.GetAll().ToListAsync();
var items = task.Result;
unitOfWork.Complete();
}
}
你也可以使用AsyncHelper
:
[UnitOfWork(isTransactional: false)]
public override void Execute(string args)
{
var items = AsyncHelper.RunSync(() => _productRepository.GetAll().ToListAsync());
}
常规工作单元方法
I create a new application service, and disable UnitOfWork, but it works fine.
Why it works fine in application service, but doesn’t work in BackgroundJob?[UnitOfWork(IsDisabled = true)] public async Task<GetAllProductsOutput> GetAllProducts() { var result = await _productRepository.GetAllListAsync(); var itemDtos = ObjectMapper.Map<List<ProductDto>>(result); return new GetAllProductsOutput { Items = itemDtos }; }
您使用的方法不同:GetAllListAsync()
与 GetAll().ToListAsync()
存储库方法是 Conventional Unit Of Work Methods,但 ToListAsync()
不是。
来自 About IQueryable<T>
上的文档:
When you call
GetAll()
outside of a repository method, there must be an open database connection. This is because of the deferred execution ofIQueryable<T>
. It does not perform a database query unless you call theToList()
method or use theIQueryable<T>
in aforeach
loop (or somehow access the queried items). So when you call theToList()
method, the database connection must be alive.