在ASP.NET Core中添加数据库驱动的调度程序的正确位置是什么? [英] What is the correct place to add a database driven scheduler in ASP.NET Core?

查看:55
本文介绍了在ASP.NET Core中添加数据库驱动的调度程序的正确位置是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have added a Timer to Startup class of an ASP.Net Core application. It fires on some time period and do operations like logging a sample text. I need it to be able to do database driven operations like adding a record to a table. So I try to get AppDbContext from DI but it is always null. Please see the code:

    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尚未准备好.

I guess I am calling Scheduler.Start in a wrong place. Seems that AppDbContext is not ready yet.

打电话给Scheduler.Start的正确位置是什么?

What is the correct place to call Scheduler.Start?

推荐答案

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

When you are running code on a background thread, you should always begin a new 'scope' for your DI container on that background thread and resolve from that scope.

所以您应该做的是:

  • 在事件内部创建新作用域
  • 从该范围解析OperationClass
  • 内部OperationClass仅依靠构造函数注入;不在服务位置上.
  • Create a new scope inside the event
  • Resolve OperationClass from that scope
  • Inside OperationClass only rely on constructor injection; not on 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取决于IServiceScopeFactory.可以从IServiceProvider解析IServiceScopeFactory.

Here Start depends on IServiceScopeFactory. IServiceScopeFactory can be resolved from the 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容器特有的文档,但此文档提供了更详细的信息有关如何在多线程应用程序中使用DI容器的信息.

Although not documentation particular to the .NET Core Container, this documentation provides a more detailed information about how to work with a DI Container in a multi-threaded application.

这篇关于在ASP.NET Core中添加数据库驱动的调度程序的正确位置是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆