0

I am implementing a BackgroundService that will perform calling a function within IJobService. I have actually used ServiceScopeFactory to inject this service inside the BackgroundService. I am still getting this error "Cannot access a disposed context instance. A common cause of this error is disposing a context instance 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 instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.\r\nObject name: 'DBContext'."


 public class BackgroundSubscriber : BackgroundService
{
    /// <summary>
    /// 
    /// </summary>
    public IServiceScopeFactory _serviceScopeFactory;
    /// <summary>
    /// 
    /// </summary>
    private readonly IConnectionMultiplexer _connectionMultiplexer;
   /// <summary>
   /// 
   /// </summary>
   /// <param name="serviceScopeFactory"></param>
   /// <param name="connectionMultiplexer"></param>
    public CustomAlertRedisSubscriber(IServiceScopeFactory serviceScopeFactory, IConnectionMultiplexer connectionMultiplexer)
    {
        _serviceScopeFactory = serviceScopeFactory;
        _connectionMultiplexer = connectionMultiplexer;
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="stoppingToken"></param>
    /// <returns></returns>
    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using (var scope = _serviceScopeFactory.CreateScope())
        {
            var scoped = scope.ServiceProvider.GetRequiredService<IJobService>();
            var sub = _connectionMultiplexer.GetSubscriber();

            return sub.SubscribeAsync($"Job", (channel, value) =>
            {
                var watcher = System.Text.Json.JsonSerializer.Deserialize<Model> 
     (value.ToString());
                scoped.BackgrounJobAsync(watcher);
            });
        }

    }
  }
}

public class JobService : IJobService
{
    private readonly IUnitOfWork _unitOfWork;

    public JobService(
        IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

  public async Task BackgrounJobAsync(Model model)
    {
            var list = await _unitOfWork.JobRepository.GetAllByIdAsync(model.id);
    }
 }

public interface IUnitOfWork : IDisposable
{
    IJobRepository JobRepository { get; }
}

 public class UnitOfWork : IUnitOfWork
 {
    private readonly TrackerDBContext _dbContext;
    private readonly AppSettings _appSettings;

    public UnitOfWork(TrackerDBContext dbContext, AppSettings appSettings)
    {
        _dbContext = dbContext;
        _appSettings = appSettings;
    }

    private IJobRepository _jobRepository;
    public IJobRepository JobRepository => _jobRepository= _jobRepository?? new 
    JobRepository(_dbContext);


    public void Dispose()
    {
        GC.SuppressFinalize(this);
    }
}

 public class JobRepository: IJobRepository
 {
    private readonly TrackerDBContext _dbContext;
    public InventoryCustomAlertsWatcherRepository(TrackerDBContext dbContext)
    {
        _dbContext = dbContext;
    }


    public async Task<Modle> GetAllByIdAsync(Modle modle)
    {
        return await _dbContext.Job.Where(x => modle.Id).ToListAsync();
    }

 }
  • This answer should help u: https://stackoverflow.com/questions/51572637/access-dbcontext-service-from-background-task .The problem is that your don't have the DB instance in your background service anymore. – D A Aug 01 '22 at 08:44

3 Answers3

0

You need to make your method async. The problem is now you are returning a Task and your method is inside a using statement. So it will dispose the scope when it is returning the Task. You need to await the SubscribeAsync.

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    using (var scope = _serviceScopeFactory.CreateScope())
    {
        var scoped = scope.ServiceProvider.GetRequiredService<IJobService>();
        var sub = _connectionMultiplexer.GetSubscriber();

        await sub.SubscribeAsync($"Job", (channel, value) =>
        {
            var watcher = System.Text.Json.JsonSerializer.Deserialize<Model> 
     (value.ToString());
            scoped.BackgrounJobAsync(watcher);
        });
    }
}
Kahbazi
  • 14,331
  • 3
  • 45
  • 76
  • I modified my code as yours, but now the error is different. while debugging when it reach to the method which makes call to the the database, an exception occurs saying Task was canceled – Rushdi Eskandar Aug 01 '22 at 10:55
0

In addition to making ExecuteAsync an async method, you also need to make the lambda passed to SubscribeAsync async. In fact, you should just go through all your code and anywhere you see *Async being called, ensure there's an await to match.

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    using (var scope = _serviceScopeFactory.CreateScope())
    {
        var scoped = scope.ServiceProvider.GetRequiredService<IJobService>();
        var sub = _connectionMultiplexer.GetSubscriber();

        await sub.SubscribeAsync($"Job", async (channel, value) =>
        {
            var watcher = System.Text.Json.JsonSerializer.Deserialize<Model> 
     (value.ToString());
            await scoped.BackgrounJobAsync(watcher);
        });
    }
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
0

For those who cope with the same error. I had more or less the same type of code as OP, also using pub/sub from Redis and BackgroundServices.

After changing IServiceProvider (which has an extension with CreateScope()) to IServiceScopeFactory it worked.

I already awaited wherever possible and no invalid using scope or Dispose was being executed. Still I think it should work, and why IServiceScopeFactory did the trick is not clear to me.

    public class PersistToDbJobHost : BackgroundService
{
    private readonly MessageQueue _messageQueue;
    private readonly IServiceScopeFactory _serviceScopeFactory;
    public PersistToDbJobHost(MessageQueue messageQueue, IServiceScopeFactory serviceScopeFactory)
    {
        _messageQueue = messageQueue;
        _serviceScopeFactory = serviceScopeFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await _messageQueue.OnMessageAsync<DocumentKey>(async (m, scopedServices) =>
        {
            var scope = _serviceScopeFactory.CreateScope();
            var persistToDbJob = scope.ServiceProvider.GetRequiredService<PersistToDbJob>();
            await persistToDbJob.OnStartAsync(m, stoppingToken);

        }, JobName.JobResults.ToString(), stoppingToken);
    }
}
Egbert Nierop
  • 2,066
  • 1
  • 14
  • 16