Как правильно добавить планировщик, управляемый базой данных, в ASP.NET Core?
Я добавил Timer
в Startup
класс ASP.Net Core
приложение. Он срабатывает в течение некоторого периода времени и выполняет такие операции, как запись образца текста. Мне нужно, чтобы он мог выполнять операции на основе базы данных, такие как добавление записи в таблицу. Поэтому я пытаюсь получить AppDbContext
от DI, но это всегда ноль. Пожалуйста, смотрите код:
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
?
2 ответа
Когда вы запускаете код в фоновом потоке, вы всегда должны начинать новую "область" для вашего 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);
}
}
Вот 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 Core Container, она содержит более подробную информацию о том, как работать с DI-контейнером в многопоточном приложении.
Вы должны позвонить внутрь ConfigureServices
после вашего AppDbContext
в коде, который вы вызываете, перед регистрацией DI. также вы можете использовать services.BuildServiceProvider()
создать IServiceProvider
содержащие услуги из предоставленного DI:
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());
}