c# - 在 ASP.NET Core 中添加数据库驱动的调度程序的正确位置是什么?

标签 c# dependency-injection asp.net-core startup

我在 ASP.Net Core 应用程序的 Startup 类中添加了一个 Timer。它会在某个时间段触发并执行诸如记录示例文本之类的操作。我需要它能够执行数据库驱动的操作,例如向表中添加记录。所以我尝试从 DI 获取 AppDbContext 但它始终为空。请看代码:

    public class Scheduler
{
    static Timer _timer;
    static bool _isStarted;
    static ILogger<Scheduler> _logger;
    const int dueTimeMin = 1;
    const int periodMin = 1;

    public static void Start(IServiceProvider serviceProvider)
    {
        if (_isStarted)
            throw new Exception("Currently is started");

        _logger = (ILogger<Scheduler>)serviceProvider.GetService(typeof(ILogger<Scheduler>));

        var autoEvent = new AutoResetEvent(false);
        var operationClass = new OperationClass(serviceProvider);
        _timer = new Timer(operationClass.DoOperation, autoEvent, dueTimeMin * 60 * 1000, periodMin * 60 * 1000);
        _isStarted = true;
        _logger.LogInformation("Scheduler started");            
    }
}

public class OperationClass
{
    IServiceProvider _serviceProvider;
    ILogger<OperationClass> _logger;
    AppDbContext _appDbContext;

    public OperationClass(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
        _logger = (ILogger<OperationClass>)serviceProvider.GetService(typeof(ILogger<OperationClass>));
        _appDbContext = (AppDbContext)_serviceProvider.GetService(typeof(AppDbContext));
    }

    public void DoOperation(Object stateInfo)
    {
        try     
        {
            _logger.LogInformation("Timer elapsed.");

            if (_appDbContext == null)
                throw new Exception("appDbContext is null");

            _appDbContext.PlayNows.Add(new PlayNow
            {
                DateTime = DateTime.Now
            });

            _appDbContext.SaveChanges();
        }
        catch (Exception exception)
        {
            _logger.LogError($"Error in DoOperation: {exception.Message}");
        }
    }
}

这是来自 Startup 的代码:

        public Startup(IHostingEnvironment env, IServiceProvider serviceProvider)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
        Configuration = builder.Build();

        AppHelper.InitializeMapper();
        Scheduler.Start(serviceProvider);
    }

我想我在错误的地方调用了 Scheduler.Start。似乎 AppDbContext 还没有准备好。

调用 Scheduler.Start 的正确位置是什么?

最佳答案

当您在后台线程上运行代码时,您应该始终在该后台线程上为您的 DI 容器开始一个新的“范围”并从该范围解析。

那么你应该做的是:

  • 在事件中创建一个新范围
  • 从该范围解析OperationClass
  • OperationClass 内部只依赖于构造函数注入(inject);不在Service Location .

您的代码应如下所示:

public class Scheduler
{
    static Timer _timer;
    const int dueTimeMin = 1;
    const int periodMin = 1;

    public static void Start(IServiceScopeFactory scopeFactory)
    {
        if (scopeFactory == null) throw new ArgumentNullException("scopeFactory");
        _timer = new Timer(_ =>
        {
            using (var scope = new scopeFactory.CreateScope())
            {
                scope.GetRequiredService<OperationClass>().DoOperation();
            }
        }, new AutoResetEvent(false), dueTimeMin * 60 * 1000, periodMin * 60 * 1000);
    }
}

这里Start依赖于IServiceScopeFactoryIServiceScopeFactory 可以从 IServiceProvider 解析。

您的 OperationClass 将变成如下所示:

public class OperationClass
{
    private readonly ILogger<OperationClass> _logger;
    private readonly AppDbContext _appDbContext;

    public OperationClass(ILogger<OperationClass> logger, AppDbContext appDbContext)
    {
        if (logger == null) throw new ArgumentNullException(nameof(logger));
        if (appDbContext == null) throw new ArgumentNullException(nameof(appDbContext));

        _logger = logger;
        _appDbContext = appDbContext;
    }

    public void DoOperation()
    {
        try     
        {
            _logger.LogInformation("DoOperation.");

            _appDbContext.PlayNows.Add(new PlayNow
            {
                DateTime = DateTime.Now
            });

            _appDbContext.SaveChanges();
        }
        catch (Exception exception)
        {
            _logger.LogError($"Error in DoOperation: {exception}");
        }
    }
}

虽然不是特定于 .NET Core 容器的文档,this documentation提供了有关如何在多线程应用程序中使用 DI 容器的更详细信息。

关于c# - 在 ASP.NET Core 中添加数据库驱动的调度程序的正确位置是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45128033/

相关文章:

c# - MeasureString 忽略 Arial 和 Times New Roman 的字体样式

.net - Fluent IOC 配置/模块的最佳位置(目前正在尝试 Ninject)

c# - .NET Core - 全局化和本地化 - 类库

c# - 包含只读项目的 ReadOnlyCollection

javascript - 使用C#创建WebAssembly

javafx - 传递参数JavaFX FXML

c# - 通过 oidc-client 登录 IdentityServer4 时出现 InvalidOperationException

asp.net-core - 如何在 .Net Core 应用程序中读取 web.config 文件

当前上下文中不存在的 C# 实例

java - 如何在 @Component 构造函数中使用 @Inject ed Spring Environment