I'd want to know how why creating instances of other classes with current database context instances as a parameter and using that db context causes this exception to be raised
'A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.'
Imma use this sample code to show the problem
public class TestController : Controller
{
private readonly DbContext dbContext;
public Controller(DbContext ctx)
{
dbContext = ctx;
}
public async Task<IActionResult> Test(string id)
{
var isValid = new otherClass(dbContext).Validate(id);
if (!isValid)
{
return View("error");
}
var user = dbContext.Users.FirstOrDefault(x => x.Id == id);
user.Age++;
dbContext.SaveChanges(); // exception is being raised here. It is second .SaveChanges() here
return View();
}
}
public class otherClass
{
private readonly DbContext dbContext;
public otherClass(DbContext ctx)
{
dbContext = ctx;
}
public bool Validate(string id)
{
var user = dbContext.Users.FirstOrDefault(x => x.Id == id);
user.ValidationAttempt = DateTime.Now;
dbContext.SaveChanges();
return user.HasConfirmedEmail;
}
}
Generally in an MVC fashion youre going to want a DbContext on a per request basis but when using threading more control through using blocks can be beneficial, an easy way to set that up would be something along the lines of
public class TestController : Controller
{
private readonly Func<DbContext> dbContext;
public Controller(Func<DbContext> ctx)
{
dbContext = ctx;
}
public async Task<IActionResult> Test(string id)
{
using(var cntx = dbContext())
{
var isValid = new otherClass(cntx).Validate(id);
if (!isValid)
{
return View("error");
}
var user = cntx.Users.FirstOrDefault(x => x.Id == id);
user.Age++;
cntx.SaveChanges();
return View();
}
}
}
that essentially resolves a new DbContext per using block - and since each thread is then handling its own DbContext - shouldnt have any issues
Use .net Core's Dependency Injection and register your DbContext as Transient.
services.AddTransient<MyContext>();
OR
services.AddDbContext<MyContext>(ServiceLifetime.Transient);
instead of
services.AddDbContext<MyContext>();
AddDbContext adds the context as scoped, which might cause trouble when working with multiple threads. Adding it as transient also has its downsides. You will not be able to make changes to some entities over multiple classes that are using the context because each class will get its own instance of your DbContext. In that instance you could use the Unit of Work pattern however.