【问题标题】:Problem with the concept of scope in Dependency injection when using EF [duplicate]使用EF时依赖注入中范围概念的问题[重复]
【发布时间】:2022-01-08 15:35:30
【问题描述】:

我对依赖注入中的作用域概念有疑问。我已将我的数据库上下文注册为范围,并且我使用异步方法将用户活动保存在表中,而不使用“等待”。

    // In Startup:

    services.AddScoped<IDbContext, StorageSystemDbContext>();

    services.AddScoped<IUserActivityService,UserActivityService>();
    

    // In UserActivityService:

    public async void LogUserActivityAsync(string controllerName, string actionName, ActionType actionType = ActionType.View, string data = "", string description = "")
    {
        await InsertAsync(new UserActivity
        {
            ControllerName = controllerName,
            ActionName = actionName,
            ActionType = actionType,
            CreatedDateTime = DateTime.Now,
            Description = description,
            UserId = (await _workContext.CurrentUserAsync())?.Id
        });
    }

    //In Controller:

    _userActivityService.LogUserActivityAsync(CurrentControllerName, CurrentActionName,data);

当我立即两次调用 same 操作时出现以下错误:

InvalidOperationException:在前一个操作完成之前,在此上下文中启动了第二个操作。这通常是由不同的线程同时使用同一个 DbContext 实例引起的。有关如何避免 DbContext 线程问题的更多信息,请参阅https://go.microsoft.com/fwlink/?linkid=2097913

我预计第二个请求会创建一个新的数据库上下文,具体取决于数据库上下文依赖注册的类型,但根据这个错误,没有为第二个请求创建一个新的上下文,而是使用了前一个。

这是什么原因?

我在 .Net Core 5 中使用 Asp Net.Core MVC 和 EF

【问题讨论】:

  • 你在使用 Blazor 吗?否则,作为 Scoped 依赖项,您将为每个 Http 请求获得一个新的 DbContext。
  • 我在 .net core 5 中使用 Asp Net.core mvc 和 EF
  • 你使用了 async void。这是一个巨大的红旗。在极少数情况下这样做是有意义的。这不是其中的一个。我建议你 watch Nick Chapsas video 这解释了为什么这很糟糕,和/或阅读 Stephen Cleary's article
  • 我不希望程序线程等待这个方法完成。将 void 改为 Task 会解决问题吗?
  • 不等待async 呼叫的唯一真正理由是您打算在类似 WaitAll() 之后继续。它不打算用作“一劳永逸”的后台线程。认真地阅读所提供的推荐材料。有非常充分的理由以这种方式处理它。

标签: entity-framework asp.net-core dependency-injection


【解决方案1】:

无论作用域如何,注入服务的 DbContext 在构造函数注入时都是一个引用。在该服务中调用多个方法将始终使用相同的实例。带有 ASP.Net 的AddedScoped 会将服务(和 DbContext)范围限定为 Web 请求。这是 DbContext 的推荐范围,以确保在请求期间加载的任何实体都可以确保它们都被同一个 DbContext 实例跟踪,并且 DbContext 在该请求的生命周期内应该是活动的。 (即,如果需要,提供延迟加载支持)瞬态范围依赖意味着传递给 2 个不同服务的 DbContext 将是不同的引用。这会导致服务 A 调用另一个服务来检索它想要与它加载并尝试更新的实体相关联的实体的问题。这些实体与不同的 DbContext 相关联,从而导致错误或创建重复数据等问题。

即使使用瞬态作用域DbContext,尝试并行运行来自同一服务的两个调用时,您仍然会遇到完全相同的问题,并且 cmets 中引用了许多充分的理由 不是 使用未等待的async 调用来执行此操作。即使您的意图是同时等待多个调用,启用类似功能的唯一方法是在方法调用本身内部限定DbContext。这通常涉及将 DbContextFactory 类型类而不是 DbContext 注入到服务中,其中 DbContextFactory 是可以初始化并提供新的 DbContext 的依赖项;那么:

using (var context = _contextFactory.Create())
{
     // operations with DbContext. (context)
}

即使这样,您也需要考虑 DB 同步保护措施,例如行和表锁定/死锁,如果您有大量并行操作发生,它们可能会引起注意。请记住,对于 Web 应用程序,Web 服务器可以并行响应大量请求,每个请求都可以随时启动这些进程。 (在使用 1 个客户端进行开发期间工作正常,在现实世界中爬行/消失。)

【讨论】:

    【解决方案2】:

    我在这里找到了答案: https://stackoverflow.com/a/44121808/4604557

    如果出于某种原因您想要运行并行数据库操作(并认为您可以避免死锁、并发冲突等),请确保每个操作都有自己的 DbContext 实例。但是请注意,并行化主要对 CPU 密集型进程有用,而不是像数据库交互这样的 IO 密集型进程。 也许您可以从并行独立读取操作中受益,但我绝对不会执行并行写入过程。除了死锁等之外,它还使得在一个事务中运行所有操作变得更加困难。

    【讨论】:

      猜你喜欢
      • 2016-12-17
      • 1970-01-01
      • 1970-01-01
      • 2020-02-01
      • 2015-11-21
      • 2016-05-18
      • 2019-11-13
      • 2017-03-20
      • 1970-01-01
      相关资源
      最近更新 更多