我想知道DbContext
类是否是线程安全的,我假设它不是,因为我正在执行在我的应用程序中访问DbContext
的并行线程并且我得到了一个锁定主机异常和其他看起来可能与线程相关的事情。
直到最近我才收到任何错误......但直到最近我还没有访问帖子中的DbContext
。
如果我是对的,人们会建议什么作为解决方案?
答案 0 :(得分:58)
这不是线程安全的。只需在您的帖子中创建DbContext
的新实例。
答案 1 :(得分:27)
不,它不是线程安全的 - 整个EF不是线程安全的,因为永远不应该共享EF上下文。
答案 2 :(得分:12)
编辑 - 下面的旧答案。
我现在总是在DbContext中使用这个模式:
using(var db = new LogDbContext())
{
// Perform work then get rid of the thing
}
我对每个请求线程的方法意味着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);
}
}
有了这个,我可以轻松编写带有可选现有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();
});
}
现在,这个方法调用可以在现有的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;
}
. . .
}
有了这个,我可以像这样得到这个线程的DbContext:
var db = LogDbContext.Current();
重要的是要注意,由于每个DbContext都保留了自己的本地缓存,因此每个线程现在都有自己独立的实体对象缓存,如果您没有为此做好准备,可能会引入一些疯狂的行为。但是,创建新的DbContext对象可能很昂贵,而且这种方法可以最大限度地降低成本。