2017-07-16 72 views
1

我已將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

+1

是的,你是在啓動流程太早調用它。直到ConfigureServices被調用之後,服務提供者才能正確配置。所以我建議你在添加數據庫上下文之後再看看那裏。我還建議使調度程序具有明確的依賴關係並將其放入服務集合中。 – Nkosi

回答

2

當您在後臺線程上運行代碼時,您應始終在該後臺線程上爲您的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取決於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}"); 
     } 
    } 
} 

雖然不是特別的文件到.NET核心容器,this documentation提供了有關如何使用DI容器在多線程工作的更詳細信息應用。

+0

感謝您的描述性答案。我應該在哪裏調用'Scheduler.Start()'? –

+0

@Afshar:你應該在[Composition Root](http://blog.ploeh.dk/2011/07/28/CompositionRoot/)中的某個地方調用'Start'。你的'Startup'方法似乎是一個不錯的地方。 – Steven

2

你的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()); 
} 
相關問題