【问题标题】:Singleton with DbContext - creating instance in Startup.cs带有 DbContext 的单例 - 在 Startup.cs 中创建实例
【发布时间】:2017-05-02 18:01:12
【问题描述】:

我正在使用 ASP.net 核心。我在将 dbcontext 实现为单例时遇到问题。

我需要我的单例 IModuleRepository 在项目开始后立即运行。所以我在Startup.cs 文件中的public void ConfigureServices(IServiceCollection services) 中创建了这个依赖的新实例。

这个单例正在使用另一个单例,所以我这样使用它:

services.AddDbContext<ModulesDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).EnableSensitiveDataLogging());
...    
services.AddSingleton<IModuleRepository, ModuleRepository>();
services.AddSingleton<ICommunicationRepository>(new CommunicationRepository(services.BuildServiceProvider().GetService<IModuleRepository>()));

在 ModuleRepository 中,我使用的是 DBcontext。

// Db context
    private readonly ModulesDbContext _modulesDbContext;

    public ModuleRepository(ModulesDbContext modulesDbContext)
    {
        _modulesDbContext = modulesDbContext;
    }

当我打电话给_modulesDbContext.SomeModel.ToList(); 时出现错误:

System.InvalidOperationException:在配置时尝试使用上下文。 DbContext 实例不能在 OnConfiguring 中使用,因为此时它仍在配置中。

项目启动后需要运行这个单例时如何避免这个错误?

感谢您的帮助。

【问题讨论】:

  • 不要显式解决依赖关系。请改用services.AddSingleton&lt;ICommunicationRepository, CommunicationRepository&gt;();
  • @IlyaChumakov 答案与下面的 juunas 相同。

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


【解决方案1】:

正如@Ilya Chumakov 所说,您可以像这样告诉 DI 容器使用您的具体类:

services.AddSingleton<ICommunicationRepository, CommunicationRepository>();

然后任何类都可以依赖ICommunicationRepository 并获取具体存储库,该存储库获取 Db 上下文。

【讨论】:

  • 我知道我能做到。但是什么会启动这个单例而不是 Startup?我需要运行构造函数,在其中创建在后台运行并定期与 Modbus 协议通信的新任务。所以我需要一开始就踢它,而不是在控制器中。
  • 如何将任务启动移动到一个方法,从服务提供者解析通信存储库并调用该方法?此外,您可以查看任务调度库来运行此类事情。
  • 您还可以在Configure 方法中解决依赖关系并从那里运行启动任务。这实际上比在构造函数中运行更有意义。
  • 当我将它移动到方法时它没有改变。从原理上讲,它与在构造函数中具有相同的东西。我运行任务没有问题。我有 DBContext 的问题。
  • True.. 也许您应该尝试将ICommunicationRepository 作为函数参数注入Configure 的方法?我通常从那里运行数据库迁移,所以我可以说 Db 上下文应该在那个时候工作。
【解决方案2】:

我发现了这个问题。这种依赖关系的调用是正确的。错误是在 CommunicationRepository 我创建了 2 Task 并且他们都使用相同的 DbContext - 所以它多次使用它。我不得不说task.Wait();

修正后CommunicationRepository构造函数中的完整代码:

// Add pernament communication
        var task = new Task(AddPernamentCommunicationAll);
        task.Start();
        task.Wait();

        // Add modules
        var taskModules = new Task(AddModulesToList);
        taskModules.Start();

感谢您的回答。

【讨论】:

  • 任务不是线程,只是调用你的代码同步。另外,不要在构造函数中运行异步任务,这很糟糕。考虑使用工厂来创建+初始化对象。话虽如此:EF Core 不是线程安全的。你不能在两个线程中使用相同的上下文(意味着:你不能并行化操作),所以你不应该将它用作单例(两个请求可能正在访问它,然后你会收到一个异常!)
  • 我知道任务与线程不同。我认为在我的情况下,在构造函数中运行异步是完全可以的。我对这些事情并不陌生。就我而言,没关系,因为如果没有任务,如果此代码出现问题,我的服务器将无法访问。是的,我想通了,EF 核心不是线程安全的——整个答案就是关于这个。我认为你脱离了上下文。我已经回答了我自己的问题,如果这有效,为什么它是-1?
  • 大卫,你运行第一个和第二个任务的方式根本没有意义。您通过向其传递操作来创建任务。只有当一个人想在另一个线程上运行AddPernamentCommunicationAll 时才会这样做。这没有任何意义,并且在 ASP.NET/ASP.NET Core 中很糟糕,因为您正在使用线程池,降低了应用程序的效率,因为您创建了一个新线程来“释放”请求线程,您仍然只使用一个线程.除了切换线程/任务上下文的开销之外,您基本上没有从中获得任何好处。
  • ASP.NET Core 在线程方面的工作方式与 WPF/桌面应用程序不同。请参阅this 文章(它适用于 ASP.NET,但仍然适用于 ASP.NET Core),尤其是“线程执行异步工作怎么样?”部分了解为什么在 ASP.NET Core 中启动非 IO 绑定任务绝对没有意义。
  • 另外,构造函数中的异步是非常糟糕。构造函数完成后,您应该初始化类,但是当您在访问类时运行异步任务时,可能会导致无效操作异常,即如果在构造函数中启动 EF db 请求,然后调用一个方法还调用一个数据库请求,您会收到一个异常,因为您尝试与您的上下文并行同时执行两个操作,这不起作用或访问尚未加载的值。如果您需要从数据库中填充一些值,请考虑使用工厂对其进行初始化
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-15
  • 2014-06-10
  • 1970-01-01
  • 1970-01-01
  • 2012-07-17
  • 1970-01-01
相关资源
最近更新 更多