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

Afs*_*bbi 3 c# dependency-injection startup asp.net-core

我添加了一个TimerStartup类的的ASP.Net Core应用。它会在某个时间段触发,并执行诸如记录示例文本的操作。我需要它能够执行数据库驱动的操作,例如将记录添加到表中。因此,我尝试AppDbContext从DI 获取信息,但它始终为null。请查看代码:

    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}");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是来自的代码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);
    }
Run Code Online (Sandbox Code Playgroud)

我想我Scheduler.Start打错地方了。似乎AppDbContext尚未准备好。

正确的通话地点是Scheduler.Start什么?

Ste*_*ven 5

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

因此,您应该做的是:

  • 在事件内创建一个新范围
  • OperationClass从该范围解决
  • 内部OperationClass只依赖构造器注入 ; 不在服务地点

您的代码应如下所示:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里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}");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

尽管不是.NET Core容器特有的文档,但该文档提供了有关如何在多线程应用程序中使用DI容器的更详细的信息。