Using Task.Run causes "object disposed" exception in my application if using DbContext
.
The code looks like (see the whole chain):
UserController.cs
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly UserService userService;
/// <summary>
/// </summary>
/// <param name="userService"></param>
public UsersController(UserService userService)
{
this.userService = userService;
}
public async Task<ActionResult> DoSomething()
{
await this.userService.MyMethod();
return this.Ok();
}
}
UserService.cs
public class UserService
{
private readonly UserRepository userRepository;
public UserService(UserRepository userRepository)
{
this.userRepository = userRepository;
}
public async Task MyMethod()
{
// some logic
Task.Run(() => MethodCallAsync());
}
void MethodCallAsync()
{
// some logic
// calls UserRepository, which uses the DbContext by DI
}
}
UserRepository:
public class UserRepository
{
private MyDbContext dbContext;
public UserRepository(MyDbContext dbContext)
{
this.dbContext = dbContext;
}
public async Task DoSomethingToo(string username)
{
var user = await this.dbContext.Users.SingleOrDefaultAsync(u => u.Username == username);
// some logic
}
}
Causes the following exception (message):
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.
How i did configure my db context and UserRepository:
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// some logic
if (MyDbContextFactory.GetConnectionString() != null)
{
services.AddDbContext<MyDbContext>(options => options.UseMySQL(MyDbContextFactory.GetConnectionString())
.LogTo(s => System.Diagnostics.Debug.WriteLine(s)));
}
services.AddScoped(typeof(UserService));
services.AddScoped(typeof(UserRepository));
// some logic
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// some logic
using (var serviceScope = app.ApplicationServices.CreateScope())
{
var dbContext = serviceScope.ServiceProvider.GetService<MyDbContext>();
if (dbContext != null)
{
dbContext.Database.Migrate();
}
}
// some logic
}
public class MysqlEntityFrameworkDesignTimeServices : IDesignTimeServices
{
public void ConfigureDesignTimeServices(IServiceCollection serviceCollection)
{
serviceCollection.AddEntityFrameworkMySQL();
new EntityFrameworkRelationalDesignServicesBuilder(serviceCollection)
.TryAddCoreServices();
}
}
MyDbContextFactory.cs
public MyDbContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseMySQL(GetConnectionString());
return new MyDbContext(optionsBuilder.Options);
}
If i replace Task.Run
with BackgroundJob.Enqueue
it works fine. But hangfire creates a lot (> 1k) of entries in redis within some minutes, since this method is called very often. Besides that, if it works with hangfire, it should also work with Task.Run,