1

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;
    }
}
2
  • @John I didnt register anything related to DbContext. Commented Nov 5, 2018 at 8:51
  • @John Ops, sorry. services.AddDbContext<DbContext> ( options => options.UseSqlServer(conn_string) ); Commented Nov 5, 2018 at 8:53

1 Answer 1

3

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

Sign up to request clarification or add additional context in comments.

7 Comments

I thoguth that "per request" means "per every instance of controller"
btw. shouldnt be there using(var cntx = new dbContext())?
@Joelty Yeah sorry, slightly poor wording on my part initially - generally a dbContext per instance of the controller is correct - though in your case as you're making use of threading then having a more direct control of context lifetime by using using blocks can be more beneficial to avoid threading issues - and if youre passing in a Func to the constructor then it wouldnt need the new keyword as the dbContext() is a shorthand for dbContext.Invoke() which just news up a DbContext using the fun
sounds cool, but how did you register it? on the other hand: I've been trying to creating new instance of dbContext basing on dbContextOptions in constructor of otherClass, but it does not save data to db :/
@Joelty since it just using Func it should just be registered in the same way as a normal DbContext so the services.AddDbContext<DbContext> ( options => options.UseSqlServer(conn_string) ) you used earlier without Func should just work.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.