多线程API应用程序中的EF核心DbContext

时间:2019-04-20 19:32:52

标签: c# entity-framework asp.net-core .net-core entity-framework-core

  

tl; dr 即使DbContext不是线程安全的,如何在多线程.NET Core API应用程序中使用实体框架?

上下文

我正在开发一个.NET Core API应用程序,它公开了几个RESTful接口来访问数据库并从中读取数据,同时运行多个TimedHostedServices作为后台工作线程,这些线程定期从其他Web服务中轮询数据并将其存储到数据库。

我知道DbContext不是线程安全的事实。我在Stackoverflow上阅读了许多文档,博客文章和答案,与此相关的很多(部分自相矛盾)答案,但在与DI一起工作时却没有真正的“最佳实践”。

我尝试过的事情

通过ServiceLifetime.Scoped扩展方法使用默认的AddDbContext会由于竞争条件而导致异常。

我不想使用锁(例如信号灯),因为明显的缺点是:

  • 代码被锁污染,并尝试/捕获/最终安全释放锁
  • 它似乎并不“健壮”,即当我忘记锁定访问DbContext的区域时。
  • 在使用还处理并发连接和访问的数据库时,在应用程序中人为同步数据库访问似乎是多余和“不自然的”

不是注入MyDbContext,而是注入DbContextOptions<MyDbContext>,仅在需要访问数据库时才构建上下文,使用using语句在读/写后立即将其处置大量的资源使用开销,不必要的连接打开/关闭。

问题

我真的很困惑:如何实现?

我认为我的用例没有什么特别之处-从后台工作人员填充数据库并从Web API层查询它-因此应该有一种使用ef core做到这一点的有意义的方法。

非常感谢!

2 个答案:

答案 0 :(得分:2)

每当TimedHostedServices触发时,您都应该创建一个范围。

在构造函数中注入服务提供者:

public MyServiceService(IServiceProvider services)
{
    _services = services;
}

然后在任务触发时创建作用域

using (var scope = _services.CreateScope())
{
    var anotherService = scope.ServiceProvider.GetRequiredService<AnotherService>();

    anotherService.Something();
}

有更完整的示例in the doc

答案 1 :(得分:0)

另一种创建自己的DbContextFactory并为每个查询实例化新实例的方法。

public class DbContextFactory
{
    public YourDbContext Create()
    {
        var options = new DbContextOptionsBuilder<YourDbContext>()
            .UseSqlServer(_connectionString)
            .Options;

        return new YourDbContext(options);
    }
}

用法

public class Service
{
    private readonly DbContextFactory _dbContextFactory;

    public Service(DbContextFactory dbContextFactory) 
         => _dbContextFactory = dbContextFactory;

    public void Execute()
    {
        using (var context = _dbContextFactory.Create())
        {
            // use context
        }
    }
}    

有了工厂,您就不再需要担心范围,并且使您的代码摆脱了ASP.NET Core的依赖。
您将能够异步执行查询,如果没有变通办法,这对于具有范围的DbContext是不可能的。
您始终对调用.SaveChanges()时保存的数据充满信心,在使用范围限定的DbContext的情况下,某些实体在其他类中发生了更改的可能性。