Как открыть соединение с базой данных в BackgroundJob в приложении ABP - PullRequest
0 голосов
/ 03 мая 2018

Выпуск

Для тестирования я создаю новую работу, она просто использует 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 в метод выполнения. Это может решить проблему. Но это не лучше для моего реального сценария. В моем реальном сценарии задание является долгосрочным и имеет много операций с БД. Если включить метод UnitOfWork for Execute, он будет блокировать ресурс БД на длительное время. Так что это не решение для моего сценария.

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

S2: Явное выполнение операции с БД в UnitOfWork. Кроме того, это может решить проблему, но я не думаю, что это лучшая практика. В моем примере, просто прочитайте данные из базы данных, транзакция не требуется. Несмотря на то, что проблема решена, но я не думаю, что это правильный путь.

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, но он работает нормально. Пожалуйста, смотрите код, как показано ниже. Почему он отлично работает в службе приложений, но не работает в 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
    };
}

1 Ответ

0 голосов
/ 03 мая 2018

Документация по Фоновые задания и рабочие использует атрибут [UnitOfWork].

S1 : добавить атрибут UnitOfWork в метод выполнения. Это может решить проблему. Но это не лучше для моего реального сценария. В моем реальном сценарии задание является долгосрочным и имеет много операций с БД. Если включить метод UnitOfWork for Execute, он будет блокировать ресурс БД на длительное время. Так что это не решение для моего сценария.

Фоновые задания выполняются синхронно в фоновом потоке, поэтому эта проблема необоснованна.

S2 : явное выполнение операции с БД в UnitOfWork. Кроме того, это может решить проблему, но я не думаю, что это лучшая практика. В моем примере, просто прочитайте данные из базы данных, транзакция не требуется. Несмотря на то, что проблема решена, но я не думаю, что это правильный путь.

Вы можете использовать нетранзакционную единицу работы :

[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());
}

Условная единица методов работы

Я создаю новую службу приложений и отключаю UnitOfWork, но она работает нормально.
Почему он хорошо работает в службе приложений, но не работает в 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()

Методы хранилища: Обычные методы работы , но ToListAsync() не один.

Из документации на О IQueryable<T>:

Когда вы вызываете GetAll() вне метода репозитория, должно быть открытое соединение с базой данных. Это из-за отложенного выполнения IQueryable<T>. Он не выполняет запрос к базе данных, пока вы не вызовете метод ToList() или не используете IQueryable<T> в цикле foreach (или каким-либо образом получите доступ к запрашиваемым элементам). Поэтому при вызове метода ToList() соединение с базой данных должно быть активным.

...