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

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

问题描述

我在运行带有Sqlite的Entity Framework Core的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:Error:
    迭代
    查询的结果时,数据库中发生异常。 System.ObjectDisposedException:安全句柄已关闭




  • Microsoft.EntityFrameworkCore.Query.RelationalQueryCompilationContextFactory:错误:
    重复
    查询的结果时,数据库中发生异常。 System.InvalidOperationException:只有在连接打开时才能调用ExecuteReader。





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

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

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

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

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



问题:


  1. 我来了吗?正确的结论,或者还有其他事情吗?

  2. 您能指出为什么错误偶尔发生的原因吗?

  3. 您知道任何简单的方法吗?避免在仍然在 DbContext 上运行并发任务的同时避免问题的方法?


解决方案

很遗憾,您不能这样做。



来自 EF Core文档


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


也来自 EF 6文档


线程安全



虽然线程安全会使异步更有用,但它是一个正交功能。鉴于EF与由用户代码组成的图进行交互以维护状态,并且没有简单的方法来确保该代码也是线程安全的,因此我们尚无法在最一般的情况下实现对它的支持。 p>

目前,EF将检测开发人员是否试图一次执行两个异步操作并抛出。







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



至于为什么偶尔会发生错误,则是其竞争状况。仅仅因为您在下一个任务之后开始两个任务(而没有等待)并不能保证数据库同时被命中。有时执行时间碰巧会对齐,而有时一个任务可能会在另一个任务开始时正确完成,因此不会发生冲突。



如何避免-不要做它不支持它。等待每个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天全站免登陆