什么是在ASP.NET Core中添加数据库驱动调度程序的正确位置?
我已将Timer
添加到Startup
类的ASP.Net Core
应用程序。它会在一段时间内触发并执行诸如记录示例文本等操作。我需要它能够执行数据库驱动的操作,如向表中添加记录。所以我尝试从DI获得AppDbContext
,但它始终为空。请参阅代码:什么是在ASP.NET Core中添加数据库驱动调度程序的正确位置?
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
只能依靠构造函数注入;不在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
。 IServiceScopeFactory
可以从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核心容器,this documentation提供了有关如何使用DI容器在多线程工作的更详细信息应用。
感谢您的描述性答案。我应该在哪里调用'Scheduler.Start()'? –
@Afshar:你应该在[Composition Root](http://blog.ploeh.dk/2011/07/28/CompositionRoot/)中的某个地方调用'Start'。你的'Startup'方法似乎是一个不错的地方。 – Steven
你的AppDbContext
已经解决之后,在DI注册之前调用它的代码中,你应该在ConfigureServices
之内调用它。你也可以使用services.BuildServiceProvider()
从所提供的DI创建一个包含IServiceProvider
服务:
public Startup(IHostingEnvironment env)
{
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();
MmHelper.InitializeMapper();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>();
services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
// ....
Scheduler.Start(services.BuildServiceProvider());
}
是的,你是在启动流程太早调用它。直到ConfigureServices被调用之后,服务提供者才能正确配置。所以我建议你在添加数据库上下文之后再看看那里。我还建议使调度程序具有明确的依赖关系并将其放入服务集合中。 – Nkosi