DbContext线程安全吗?

jcv*_*dan 53 c# thread-safety entity-framework-4.1

我想知道DbContext该类是否是线程安全的,我假设它不是,因为我正在执行并行访问DbContext我的应用程序中的并行线程,我得到了许多锁定异常和其他看起来可能与线程相关的东西.

直到最近我还没有收到任何错误...但直到最近我还没有访问DbContext线程.

如果我是对的,人们会建议什么作为解决方案?

Dan*_*elB 59

这不是线程安全的.只需DbContext在您的线程中创建一个新实例.

  • 这也是我的方法.但是,这有多安全(不是线程,但是db-wise)?正如克里斯在答案中所说,我应该期待有什么奇怪的行为吗? (3认同)

Lad*_*nka 27

不,它不是线程安全的 - 整个EF不是线程安全的,因为永远不应该共享EF上下文.

  • 所以你真的应该在每次想要使用它时创建 dbcontext 吗?目前我只有一个 dbcontext 字段作为我的存储库类的一部分,我想在需要时创建它可以解决这个问题...... (2认同)
  • 如果单个逻辑事务使用新的存储库实例并且存储库是线程安全的,则每个存储库可以有一个上下文. (2认同)

Chr*_*ini 12

编辑 - 下面的旧答案.

我现在总是在DbContext中使用这个模式:

using(var db = new LogDbContext())
{
    // Perform work then get rid of the thing
}
Run Code Online (Sandbox Code Playgroud)

我对每个请求线程一个的方法意味着DbContext中的缓存对象会粘在一起并变得陈旧,即使其他DbContext实例正在将新值写入其后面的实际数据库.这会产生一些奇怪的问题,例如一个请求执行插入,下一个请求列表进入另一个线程,该线程具有该查询的缓存,陈旧的数据列表.

有些方法可以实现以下功能,甚至可以提高多读/少写样式应用程序的性能,但它们比上面简单的模式采用更多的设计和策略.

更新

我还为库方法使用了一个有用的辅助方法,比如记录调用.这是辅助方法:

    public static async Task Using(Db db, Func<Db, Task> action)
    {
        if (db == null)
        {
            using (db = new Db())
            {
                await action(db);
            }
        }
        else
        {
            await action(db);
        }
    }
Run Code Online (Sandbox Code Playgroud)

有了这个,我可以轻松编写采用可选的现有DbContext的代码,或者在使用上下文中实例化一个代码,具体取决于它是如何被调用的.

例如,在使用DbContext时,我可能会加载一些数据,记录一些信息,然后保存该数据 - 从性能角度来看,最好使用相同的DbContext完成所有这些操作.另一方面,我可能还想记录一些响应简单操作的内容,既不加载也不写任何其他数据.通过利用上面的方法,我可以只使用一种日志记录方法,无论您是否想在现有的DbContext中工作,它都有效:

public async Task WriteLine(string line, Db _db = null)
{
    await Db.Using(_db, db => {
        db.LogLines.Add(new LogLine(line));
        await db.SaveChangesAsync();
    });
}
Run Code Online (Sandbox Code Playgroud)

现在这个方法调用可以在现有的DbContext内部或外部调用,并且仍然以正确的方式运行,而不是必须拥有此版本的2个版本以及我拥有的所有其他便利日志记录方法或其他实用程序方法,而不必知道和计划将对他们或他们的呼叫者进行的每次通话的背景.这基本上回到了我下面的线程静态策略的好处之一,我不必担心在实用程序调用中确切地打开数据库时应该担心它.

老答案

我通常使用EF DbContext处理线程安全,如下所示:

public class LogDbContext : DbContext
{
    . . .

    [ThreadStatic]
    protected static LogDbContext current;

    public static LogDbContext Current()
    {
        if (current == null)
            current = new LogDbContext();

        return current;
    }

    . . .
}
Run Code Online (Sandbox Code Playgroud)

有了这个,我可以得到这个线程的DbContext,如下所示:

var db = LogDbContext.Current();
Run Code Online (Sandbox Code Playgroud)

重要的是要注意,由于每个DbContext都保留了自己的本地缓存,因此每个线程现在都有自己独立的实体对象缓存,如果您没有为此做好准备,可能会引入一些疯狂的行为.但是,创建新的DbContext对象可能很昂贵,而且这种方法可以最大限度地降低成本.