sqlite 避免在同一DbContext上同时运行多个任务时出现实体框架错误

pcww981p  于 2022-11-14  发布在  SQLite
关注(0)|答案(2)|浏览(212)

我在一个运行带有Sqlite的实体框架核心的DotNet Core项目中有一个WebApi控制器。
动作中的此代码有时会产生错误:

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

这些错误是:

  • Microsoft.EntityFrameworkCore.Query.RelationalQueryCompilationContextFactory:Error:迭代查询结果时,数据库中出现异常。System.ObjectDisposedException:安全句柄已关闭
  • Microsoft.EntityFrameworkCore.Query.RelationalQueryCompilationContextFactory:Error:迭代查询结果时,数据库中出现异常。System.InvalidOperationException:只有在连接打开时才能调用ExecuteReader。

在我看来,这两个错误都表明DbContext发生了一些事情,比如过早处置(尽管不是DbContext本身)。DbContext使用DotNet Core的管道“通常”注入到控制器构造函数中,配置如下所示(来自我的Startup.csConfigureServices方法体):

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

如果我将生成上述代码的错误更改为如下所示:

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

..。我没有看到提到的错误,因此得出的结论是,在我的DbContext(如上所述注入)的同一示例上并发运行多个任务是导致问题的原因。显然,这是一个意想不到的问题。
问题:
1.我得出的结论是正确的吗,还是有别的事情在发生?
1.你能指出为什么这些错误只是偶尔发生吗?
1.您是否知道在DbContext上仍在运行并发任务的同时避免该问题的简单方法?

gz5pxeao

gz5pxeao1#

不幸的是,你不能这么做。
EF Core documentation
EF Core不支持在同一上下文示例上运行多个并行操作。在开始下一个操作之前,您应该始终等待操作完成。这通常是通过在每个异步操作上使用AWAIT关键字来完成的。
同样来自EF 6文档

线程安全

虽然线程安全会使异步更有用,但它是一个正交特性。目前还不清楚在最一般的情况下我们是否能实现对它的支持,因为EF与由用户代码组成的图交互来维护状态,而且没有简单的方法来确保该代码也是线程安全的。
目前,EF将检测开发人员是否试图一次执行两个异步操作并抛出。
DbContext在任何时间点仅支持单个开放数据读取器。如果希望同时执行多个数据库查询,则需要多个DbContext示例,每个示例对应一个并发查询。
至于为什么错误偶尔会发生,这是一个竞争条件。仅仅因为您一个接一个地启动两个任务(不等待),并不能保证数据库会同时命中。有时执行时间恰好一致,而有时一个任务可能在另一个任务开始时正确完成,因此不存在冲突。
如何避免它-不要这样做,因为它不受支持。等待您的每个DbContext调用或使用多个DbContext示例。

  • 查询指任何数据库操作,包括SELECT、UPDATE、DELETE、INSERT、ALTER、存储过程调用等*
kxkpmulp

kxkpmulp2#

根据文档,EF当然不支持在同一上下文示例上运行多个并行操作,但是您可以使用DbConextOptions为相同的数据库上下文创建其他上下文

private readonly DbContextOptions<ApplicationDbContext> _dbOptions;

然后您可以在您的控制器中使用

public MyController(DbContextOptions<ApplicationDbContext> dbOptions)
    {
        _dbOptions = dbOptions;
    }

现在在您的方法中,您可以调用任务,每个任务都将使用自己的上下文,例如

public async Task<IActionResult> MyView()
    {
        var dataAwesome = ParallelAwesome();
        var dataBazinga = ParallelBazinga();
     
        await Task.WhenAll(dataAwesome, dataBazinga);
        // here we wait for all of them, once their ar done
        // you can get data from Result
        r1=dataAwesome.Result;
        r2=dataBazinga.Result;
        // do what you want with r1 and r2
        return MyView();

    }

并且您必须编写以下函数

private Task<Awesome> ParallelAwesome()
    {
        var localContext = new ApplicationDbContext(_dbOptions);

        var result = localContext.Awesome.FirstOrDefaultAsync(a => [...]); // notice we do not use await
        return result;        
    }

private Task<Bazinga> ParallelBazinga()
    {
        var localContext = new ApplicationDbContext(_dbOptions);

        var result = localContext.Bazinga.FirstOrDefaultAsync(a => [...]);
        // notice we do not use await
        return result;        
    }

相关问题