如何在 ABP 应用程序的 BackgroundJob 中打开数据库连接 [英] How to open database connection in a BackgroundJob in ABP application

查看:31
本文介绍了如何在 ABP 应用程序的 BackgroundJob 中打开数据库连接的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题

为了测试,我创建了一个新作业,它只是使用 IRepository 从数据库读取数据.代码如下:

公共类 TestJob : BackgroundJob, ITransientDependency{private readonly IRepository_productRepository;私有只读 IUnitOfWorkManager _unitOfWorkManager;public TestJob(IRepository productRepository,IUnitOfWorkManager unitOfWorkManager){_productRepository = productRepository;_unitOfWorkManager = unitOfWorkManager;}公共覆盖无效执行(字符串参数){var task = _productRepository.GetAll().ToListAsync();var items = task.Result;Debug.WriteLine("测试数据库连接");}}

然后我创建一个新的应用程序服务来触发作业.代码片段如下:

公共异步任务 UowInJobTest(){await _backgroundJobManager.EnqueueAsync("aaaa");}

当我测试作业时,它会在执行 var task = _productRepository.GetAll().ToListAsync();

时抛出以下异常

无法访问已处置的对象.此错误的一个常见原因是处理从依赖注入解析的上下文,然后尝试在应用程序的其他地方使用相同的上下文实例.如果您在上下文上调用 Dispose() 或将上下文包装在 using 语句中,则可能会发生这种情况.如果你使用依赖注入,你应该让依赖注入容器来处理上下文实例.对象名称:'AbpExampleDbContext'.

解决方案

S1:在 execute 方法上添加 UnitOfWork 属性.它可以解决问题.但这对我的实际情况并不好.在我的实际场景中,作业是一个长时间的任务,并且有很多数据库操作,如果为 Execute 方法启用 UnitOfWork,它将长时间锁定数据库资源.所以这不是我的方案的解决方案.

[UnitOfWork]公共覆盖无效执行(字符串参数){var task = _productRepository.GetAll().ToListAsync();var items = task.Result;Debug.WriteLine("测试数据库连接");}

S2: 在 UnitOfWork 中显式执行 DB 操作.此外,这可以解决问题,但我认为这不是最佳实践.在我的例子中,只从数据库读取数据,不需要事务.尽管问题已解决,但我认为这不是正确的方法.

public override void Execute(string args){使用 (var unitOfWork = _unitOfWorkManager.Begin()){var task = _productRepository.GetAll().ToListAsync();var items = task.Result;unitOfWork.Complete();}Debug.WriteLine("测试数据库连接");}

问题

我的问题是在 BackgroundJob 中执行数据库操作的正确和最佳方法是什么?

还有另外一个问题,我创建了一个新的应用程序服务,并禁用了 UnitOfWrok,但它工作正常.请看下面的代码.为什么它在应用服务中运行良好,而在BackgroundJob中不起作用?

[UnitOfWork(IsDisabled =true)]公共异步任务获取所有产品(){var 结果 = await _productRepository.GetAllListAsync();var itemDtos = ObjectMapper.Map>(result);返回新的 GetAllProductsOutput(){项目 = itemDtos};}

解决方案

关于背景工作和工人的文档 使用 [UnitOfWork] 属性.

<块引用>

S1:在 execute 方法上添加 UnitOfWork 属性.它可以解决问题.但这对我的实际情况并不好.在我的实际场景中,作业是一个长时间的任务,并且有很多数据库操作,如果为 Execute 方法启用 UnitOfWork,它将长时间锁定数据库资源.所以这不是我的方案的解决方案.

后台作业在后台线程上同步运行,因此这种担忧是没有根据的.

<块引用>

S2:在 UnitOfWork 中显式执行 DB 操作.此外,这可以解决问题,但我认为这不是最佳实践.在我的例子中,只从数据库读取数据,不需要事务.尽管问题已解决,但我认为这不是正确的方法.

您可以使用 Non-Transactional工作单元:

[UnitOfWork(isTransactional: false)]公共覆盖无效执行(字符串参数){var task = _productRepository.GetAll().ToListAsync();var items = task.Result;}

您可以使用IUnitOfWorkManager:

public override void Execute(string args){使用 (var unitOfWork = _unitOfWorkManager.Begin(TransactionScopeOption.Suppress)){var task = _productRepository.GetAll().ToListAsync();var items = task.Result;unitOfWork.Complete();}}

你也可以使用AsyncHelper:

[UnitOfWork(isTransactional: false)]公共覆盖无效执行(字符串参数){var items = AsyncHelper.RunSync(() => _productRepository.GetAll().ToListAsync());}

传统的工作单元方法

<块引用>

我创建了一个新的应用程序服务,并禁用了 UnitOfWork,但它工作正常.
为什么在应用服务中可以正常使用,而在BackgroundJob中却不能使用?

[UnitOfWork(IsDisabled = true)]公共异步任务获取所有产品(){var 结果 = await _productRepository.GetAllListAsync();var itemDtos = ObjectMapper.Map>(result);返回新的 GetAllProductsOutput{项目 = itemDtos};}

您正在使用不同的方法:GetAllListAsync()GetAll().ToListAsync()

存储库方法是 传统的工作单元方法,但ToListAsync() 不是一个.

来自 关于IQueryable:

<块引用>

当您在存储库方法之外调用 GetAll() 时,必须有一个打开的数据库连接.这是因为 IQueryable 的延迟执行.它不会执行数据库查询,除非您调用 ToList() 方法或在 foreach 循环中使用 IQueryable(或以某种方式访问查询的项目).所以当你调用ToList()方法时,数据库连接必须是活动的.

Issue

For testing, I create a new job, it just use IRepository to read data from database. The code as below:

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");
    }
}

Then I create a new application service to trigger the job. The code snippet as below:

public async Task UowInJobTest()
{
   await  _backgroundJobManager.EnqueueAsync<TestJob, string>("aaaa");
}

When I test the job, It will throw following exception when execute 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'.

Solution

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.

[UnitOfWork]
public override void Execute(string args)
{
    var task = _productRepository.GetAll().ToListAsync();
    var items = task.Result;
    Debug.WriteLine("test db connection");
}

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.

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");
}

Question

My question is what’s the correct and best way to execute a DB operation in BackgroundJob?

There is addtional another question, I create a new application service, and disable UnitOfWrok, but it works fine. Please see the code as below. 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
    };
}

解决方案

The documentation on Background Jobs And Workers uses [UnitOfWork] attribute.

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.

Background jobs are run synchronously on a background thread, so this concern is unfounded.

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.

You can use a Non-Transactional Unit Of Work:

[UnitOfWork(isTransactional: false)]
public override void Execute(string args)
{
    var task = _productRepository.GetAll().ToListAsync();
    var items = task.Result;
}

You can use 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();
    }
}

You can also use AsyncHelper:

[UnitOfWork(isTransactional: false)]
public override void Execute(string args)
{
    var items = AsyncHelper.RunSync(() => _productRepository.GetAll().ToListAsync());
}

Conventional Unit Of Work Methods

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
    };
}

You are using different methods: GetAllListAsync() vs GetAll().ToListAsync()

Repository methods are Conventional Unit Of Work Methods, but ToListAsync() isn't one.

From the documentation on 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.

这篇关于如何在 ABP 应用程序的 BackgroundJob 中打开数据库连接的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆