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