Darren Wainwright
Darren Wainwright

Reputation: 30727

EF Core DbContext being disposed of before Async methods are complete

I am having issues where my DbContext is being disposed of early. It is only ever apparent when calling any of the *Async methods, such as ToListAsync() - if i call any of the syncronous methods everything is fine.

I can't figure out what I'm doing wrong.

Any advise please?

Here is as much of the code as i believe is needed.

The DbContext and its interface

public interface IMyDbContext
{
    DbSet<MyModel> MyModels { get; set; }
}

public class MyDbContext : DbContext, IMyDbContext
{
    public DbSet<MyModel> MyModels { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);

    }

    public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new MyModelConfig());           
    }
}

A Repository using this DbContext

public class MyModelRepository : IMyModelRepository
{
    private readonly IMyDbContext _dbContext;
    private string _baseSql = "Some SQL here ";

    public MyModelRepository(IMyDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<IList<MyModel>> GetAllAsync(Paging paging, Permission permission)
    {

        if (permission == null)
            throw new ArgumentNullException("permission");

        string sql = ApplyFilter(_baseSql, permission);

        try
        {
            // THIS FAILS
            return await _dbContext.MyModels.FromSql(sql).Skip(paging.Skip).Take(paging.Take).ToListAsync();

            // THIS FAILS
            return await _dbContext.MyModels.FromSql(sql).ToListAsync();

            // THIS WORKS
            return await _dbContext.MyModels.FromSql(sql).ToList(); 

        }
        catch (Exception e)
        {

            throw new InvalidOperationException("Could not retrieve data", e);
        }
    }
}

I'm calling the repo via a service that looks like this:

public class GetAllMyModelQuery : IGetAllMyModelQuery
{
    private readonly IMyModelRepository _myModelRepository;
    private readonly IPermissionService _permissionService;
    private readonly ILogger _logger;

    public GetAllAbsenceQuery(IMyModelRepository myModelRepository, IPermissionService permissionService, ILogger<GetAllMyModelQuery> logger)
    {
        _myModelRepository = myModelRepository;
        _permissionService = permissionService;
        _logger = logger;
    }

    public async Task<IList<Emp_AbsenceEtrac>> Execute(Paging paging)
    {
        if (_permissionService.Permission == null)
        {
            _logger.LogInformation("No permission to the requested resource");
            return null;
        }

        // if external?
        // call external repo

        //TODO//

        // else
        return await _myModelRepository.GetAllAsync(paging, _permissionService.Permission);
    }
}

This in turn is called by the controller

public class MyModelController : Controller
{
    private readonly IQueryStore _queryStore;

    public MyModelController(IQueryStore queryStore)
    {
        _queryStore = queryStore;
    }

    [HttpGet]
    [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
    [ProducesResponseType(typeof(BadRequestObjectResult), (int)HttpStatusCode.BadRequest)]
    public async Task<IActionResult> Index([FromQuery] int offset = 0, [FromQuery] int limit = 25)
    {
        Paging paging = new Paging(offset, limit);
        return Ok(_queryStore.GetAllMyModelQuery.Execute(paging));
    }
}

Finally, it's all wired together in the startup:

services.AddScoped<IMyDbContext, MyDbContext>();
services.AddScoped<IMyModelRepository, MyModelRepository>();    
// Everything else above is also added as scope..

 services.AddDbContext<MyDbContext>(opts =>
{
    opts.UseSqlServer(Configuration.GetConnectionString("MyDb"),
        sqlServerOptions =>
        {
            sqlServerOptions.CommandTimeout(600);
            // required to allow skip/take on sql server 2008
            sqlServerOptions.UseRowNumberForPaging(true);
        });
}); 

Is there anything jumping out that would cause my Async calls to result in a closed Db connection?

Error is:

enter image description here

Upvotes: 3

Views: 2058

Answers (1)

mm8
mm8

Reputation: 169150

You should await the GetAllMyModelQuery.Execute method in your Index controller action:

[HttpGet]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(BadRequestObjectResult), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Index([FromQuery] int offset = 0, [FromQuery] int limit = 25)
{
    Paging paging = new Paging(offset, limit);
    return Ok(await _queryStore.GetAllMyModelQuery.Execute(paging).ConfigureAwait(false));
}

Upvotes: 2

Related Questions