Search code examples
c#asp.netentity-frameworkasynchronousaction-filter

Context has been disposed. Execute Async


There is a problem with async executing query to web api using entity framework. General view such that a request is sent to API and ActionFilter catch request to function of controller, send to client status ok with response key, execute request async and after send data by SignalR. ActionFilter starts async executing like this:

HostingEnvironment.QueueBackgroundWorkItem(async (ct) =>
{
    var response = await actionContext.ActionDescriptor.ExecuteAsync(actionContext.ControllerContext,
    actionContext.ActionArguments, ct);
    var data = new JavaScriptSerializer().Serialize(response);
    await connectionContext.Connection.Send(connectionId, $"{requestKey};{data}");
});

Controller:

[HttpPost]
[Route("")]
public ICollection<TradeAccountModel> GetAll()
{
    using (var ls = _lifetimeScope.BeginLifetimeScope())
    {
        return _tradeAccountService.GetAll();
    }
}

Service:

public ICollection<TradeAccountModel> GetAll()
{
    using (_tradeAccountRepository.BeginTransaction())
    {
        return _tradeAccountRepository.Get().Select(acc => acc.ToModel());
    }
}

Respository uses UOW pattern. And when repository try to get data from DB there is error: System.InvalidOperationException: The operation cannot be completed because the DbContext has been disposed.

TDataRepository containts common operations and extends BaseDataRespository, such as GetById and ect

public interface ITradeRepository: ITDataRepository<TradeAccount>
{
}

internal class TradeRepository : T1DataRepository<TradeAccount>,
    ITradeRepository
{

}



IEnumerable<TEntity> ITDataRepository<TEntity>.Get()
    {
        return base.Get<TEntity>();
    }

BaseDataRespository has BeginTransaction method

public IDisposable BeginTransaction()
    {
        if (_scope == null)
        {
            _scope = new TransactionScope(
                TransactionScopeOption.Required,
                new TransactionOptions()
                {
                    IsolationLevel = IsolationLevel.ReadCommitted,
                    Timeout = TimeSpan.FromSeconds(300)
                },
                TransactionScopeAsyncFlowOption.Enabled);
        }

        return _scope;
    }

Context creates by BaseDataRespository

private TransactionScope _scope;
    private readonly Lazy<DataContext> _contextFactory;

    private DataContext Context => _contextFactory.Value;

    public BaseDataRepository()
    {
        _contextFactory = new Lazy<DataContext>(()=> 
        {
            var ctx = CreateContext();

            ctx.FireBuild += Build;

            return ctx;
        });
    }

Solution

  • If your DbContext is registered as scoped service / HTTP request scoped service, you should refrain from passing DbContext from your request pipeline to your background task.

    This is because as per the docs:

    Schedules a task which can run in the background, independent of any request.

    Any scoped services that also implement IDisposable will be automatically disposed after the request completes.

    You should activate new DbContext using an independent scope in your controller action / request pipeline, and pass it to your background task.

    This docs is for .Net Core, but it may give you ideas how scoped services could be used in a background task.