避免在同一 DbContext 上同时运行多个任务的实体框架错误 [英] Avoid Entity Framework Error with Multiple Tasks Running Concurrently on Same DbContext

查看:34
本文介绍了避免在同一 DbContext 上同时运行多个任务的实体框架错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在运行 Entity Framework Core 和 Sqlite 的 Dotnet Core 项目中有一个 WebApi 控制器.

这个动作中的代码偶尔会产生错误:

var t1 = _dbContext.Awesome.FirstOrDefaultAsync(a => [...]);var t2 = _dbContext.Bazinga.FirstOrDefaultAsync(b => [...]);var r1 = 等待 t1;var r2 = 等待 t2;

错误是:

  • Microsoft.EntityFrameworkCore.Query.RelationalQueryCompilationContextFactory:错误:迭代结果时数据库发生异常询问.System.ObjectDisposedException:安全句柄已关闭

  • Microsoft.EntityFrameworkCore.Query.RelationalQueryCompilationContextFactory:错误:迭代结果时数据库发生异常询问.System.InvalidOperationException: ExecuteReader 只能是连接打开时调用.

对我来说,这两个错误都表明 DbContext 发生了一些事情,比如过早处置(尽管不是 DbContext 本身).DbContext 正在使用 Dotnet Core 的常规方式"的管​​道注入控制器构造函数,配置如下所示(来自我的 Startup.cs ConfigureServices 方法体):

services.AddDbContext(options => options.UseSqlite(connectionString));

如果我将上面的错误生成代码更改为:

var r1 = await _dbContext.Awesome.FirstOrDefaultAsync(a => [...]);var r2 = await _dbContext.Bazinga.FirstOrDefaultAsync(b => [...]);

...我没有看到提到的错误,因此得出的结论是,在我的 DbContext 的同一个实例上同时运行多个任务(如上所述注入)是导致问题的原因.显然,这是一个意外.

问题:

  1. 我得出了正确的结论,还是有其他事情发生?
  2. 您能否指出错误只是偶尔发生的原因?
  3. 您是否知道在 DbContext 上仍然运行并发任务的同时避免该问题的任何简单方法?

解决方案

很遗憾,您不能这样做.

来自 EF Core 文档

<块引用>

EF Core 不支持在同一上下文实例上运行多个并行操作.在开始下一个操作之前,您应该始终等待操作完成.这通常通过在每个异步操作上使用 await 关键字来完成.

同样来自 EF 6文档

<块引用>

线程安全

虽然线程安全会使异步更有用,但它是一个正交特性.考虑到 EF 与由用户代码组成的图形交互以维护状态,并且没有简单的方法来确保此代码也是线程安全的,因此目前尚不清楚我们是否可以在最一般的情况下实现对它的支持.

目前,EF 会检测开发者是否尝试同时执行两个异步操作并抛出.


A DbContext 在任何时间点仅支持单个开放数据读取器.如果您想同时执行多个数据库查询,您将需要多个 DbContext 实例,每个并发查询一个.

至于为什么偶尔会出现错误,这是一个竞争条件.仅仅因为您一个接一个地开始 2 个任务(不等待)并不能保证数据库会同时被命中.有时执行时间恰好对齐,有时一个任务可能会在另一个任务开始时完成,因此没有冲突.

如何避免它 - 不要这样做,因为它不受支持.等待每个 DbContext 调用或使用多个 DbContext 实例.

查询是指任何数据库操作,包括SELECT、UPDATE、DELETE、INSERT、ALTER、STORED PROCEDURE CALL、ETC

I have a WebApi controller in a Dotnet Core project running Entity Framework Core with Sqlite.

This code in an action occationally produces errors:

var t1 = _dbContext.Awesome.FirstOrDefaultAsync(a => [...]);
var t2 = _dbContext.Bazinga.FirstOrDefaultAsync(b => [...]);
var r1 = await t1;
var r2 = await t2;

The errors have been:

  • Microsoft.EntityFrameworkCore.Query.RelationalQueryCompilationContextFactory:Error: An exception occurred in the database while iterating the results of a query. System.ObjectDisposedException: Safe handle has been closed

  • Microsoft.EntityFrameworkCore.Query.RelationalQueryCompilationContextFactory:Error: An exception occurred in the database while iterating the results of a query. System.InvalidOperationException: ExecuteReader can only be called when the connection is open.

Both errors to me suggests somethings going on with the DbContext, like premature disposing (albeit not of the DbContext itself). The DbContext is being injected in the controller constructor using the plumbing of Dotnet Core "the usual way", configuration as shown below (from my Startup.cs ConfigureServices method body):

services.AddDbContext<ApplicationContext>(options => options.UseSqlite(connectionString));

If I change the error producing code above to something like:

var r1 = await _dbContext.Awesome.FirstOrDefaultAsync(a => [...]);
var r2 = await _dbContext.Bazinga.FirstOrDefaultAsync(b => [...]);

... I haven't seen the errors mentioned, hence the conclusion that running multiple tasks concurrently on the same instance of my DbContext (injected as described above) is what's causing the issue. Obviously, it's an unexpected one.

Questions:

  1. Have I come to the right conclusion, or is there something else going on?
  2. Can you pinpoint why the errors occur only occasionally?
  3. Do you know of any simple way to avoid the issue while still running concurrent tasks on the DbContext?

解决方案

Unfortunately you can't do that.

From the EF Core documentation

EF Core does not support multiple parallel operations being run on the same context instance. You should always wait for an operation to complete before beginning the next operation. This is typically done by using the await keyword on each asynchronous operation.

Also from the EF 6 documentation

Thread Safety

While thread safety would make async more useful it is an orthogonal feature. It is unclear that we could ever implement support for it in the most general case, given that EF interacts with a graph composed of user code to maintain state and there aren't easy ways to ensure that this code is also thread safe.

For the moment, EF will detect if the developer attempts to execute two async operations at one time and throw.


A DbContext only supports a single open data reader at any point in time. If you want to execute multiple simultaneous database queries you will need multiple DbContext instances, one for each concurrent query.

As to why the error occurs occasionally, its a race condition. Just because you start 2 tasks one after the next (without awaiting) does not guarantee that the database will be hit at the same time. Sometimes the execution times happen to line up and other times one task might finish right as the other task starts so there is no conflict.

How to avoid it - don't do it as its not supported. Await each of your DbContext calls or use multiple DbContext instances.

by query I mean any DB operation including SELECT, UPDATE, DELETE, INSERT, ALTER, STORED PROCEDURE CALL, ETC

这篇关于避免在同一 DbContext 上同时运行多个任务的实体框架错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆