如何避免EF Core中不安全的上下文操作?

时间:2018-11-05 08:42:48

标签: c# asp.net-core asp.net-core-mvc entity-framework-core

我想知道为什么使用当前数据库上下文实例作为参数创建其他类的实例并使用该数据库上下文会引发此异常的原因

  

'在先前的操作完成之前,在此上下文中开始了第二次操作。不能保证任何实例成员都是线程安全的。'

Imma使用此示例代码来显示问题

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 个答案:

答案 0 :(得分:1)

使用.net Core的依赖项注入并将您的DbContext注册为瞬态。

services.AddTransient<MyContext>();

OR

services.AddDbContext<MyContext>(ServiceLifetime.Transient);

代替

services.AddDbContext<MyContext>();

AddDbContext将范围添加为上下文,这在使用多个线程时可能会引起麻烦。将其添加为瞬态也有其缺点。您将无法在使用上下文的多个类上对某些实体进行更改,因为每个类都会获得自己的DbContext实例。但是,在那种情况下,您可以使用工作单位模式。

答案 1 :(得分:1)

通常,以MVC方式,您将希望在每个请求的基础上获得DbContext,但是当使用线程时,通过使用块进行更多控制可能是有益的,一种简单的设置方法将是

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();
    }
    }
}

从本质上讲,每个using块都会解析一个新的DbContext-并且由于每个线程随后都在处理自己的DbContext-应该不会有任何问题