How to avoid not-safe context operations in EF Core?

asp.net-core asp.net-core-mvc c# entity-framework-core

Question

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;
    }
}
1
0
11/5/2018 8:42:47 AM

Accepted Answer

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

2
11/5/2018 11:25:24 AM

Popular Answer

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.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow