2016-11-24 22 views
4

我想在一個asp.NET MVC應用程序中使用Quartz.Net。我使用Unity作爲DI,並使用PerRequestLifeTimeManagerQuartz.net的統一生命週期管理器

然而,Quartz.Net不能與PerRequestLifeTimeManager一起使用,因爲它沒有開始的請求。我試圖用它解決的任何依賴項都返回null。

我創建了一個類像一個適配器使用根據上下文兩項終生的時間管理者如下:

class CustomLifetimeManager : LifetimeManager 
{ 
    private readonly string _key = "CustomLifetimeManagerKey" + Guid.NewGuid(); 
    private readonly PerResolveLifetimeManager _perResolveLifetimeManager = new PerResolveLifetimeManager(); 

    private bool IsWebContext => HttpContext.Current != null; 

    public override object GetValue() 
    { 
     return IsWebContext 
      ? HttpContext.Current.Items[_key] 
      : _perResolveLifetimeManager.GetValue(); 
    } 

    public override void SetValue(object newValue) 
    { 
     if (IsWebContext) 
      HttpContext.Current.Items[_key] = newValue; 
     else 
      _perResolveLifetimeManager.SetValue(newValue); 
    } 

    public override void RemoveValue() 
    { 
     if (IsWebContext) 
      HttpContext.Current.Items[_key] = null; 
     else 
      _perResolveLifetimeManager.RemoveValue(); 
    } 
} 

我試過PerThreadLifetimeManager,它執行罰款的第一次,那麼後續執行失敗,並顯示消息

該操作無法完成,因爲DbContext已處理了 。

我試圖改變到PerResolveLifeTimeManager,但它失敗

的實體對象不能由 多個實例IEntityChangeTracker

引用

我的工作是非常簡單的,類似如下:

[DisallowConcurrentExecution] 
class MyJob 
{ 
    IFooRepository _fooRepository; 
    IBarRepository _barRepository; 
    public MyJob(IFooRepository fooRepository, IBarRepository barRepository) 
    { 
     _fooRepository = fooRepository; 
     _barRepository = barRepository; 
    } 

    public void Execute(IJobExecutionContext context) 
    { 
     var foos = _fooRepository.Where(x => !x.Processed); 

     foreach(var foo in foos) 
     { 
      var bar = _barRepository.Where(x => x.Baz == foo.Baz); 
      foo.DoMagic(bar); 
      foo.Processed = true; 
      _fooRepository.Save(foo); 
     } 
    } 
} 

而我的作業工廠是

public class UnityJobFactory : IJobFactory 
{ 
    private readonly IUnityContainer _container; 

    public UnityJobFactory(IUnityContainer container) 
    { 
     _container = container; 
    } 

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) 
    { 
     return (IJob)_container.Resolve(bundle.JobDetail.JobType); 
    } 

    public void ReturnJob(IJob job) 
    { 

    } 
} 

如何正確管理石英工作中的依賴項生命時間?

回答

1

很久以前,用simpleinjector得到了這個工作。 這與舊版本的石英不過,希望它還能幫助

你需要創建一個自定義LifetimeScope

public class LifetimeScopeJobDecorator : IJob 
{ 
    private readonly IJob _decoratee; 
    private readonly Container _container; 

    public LifetimeScopeJobDecorator(IJob decoratee, Container container) 
    { 
     _decoratee = decoratee; 
     _container = container; 
    } 

    public void Execute(IJobExecutionContext context) 
    { 
      using (_container.BeginLifetimeScope()) 
      { 
       _decoratee.Execute(context); 
      } 
    } 
} 

您致電到你的工作的工廠

public class SimpleInjectorJobFactory : IJobFactory 
{ 
    private readonly Container _container; 

    public SimpleInjectorJobFactory(Container container) 
    { 
     _container = container; 
     _container.Verify(); 
    } 

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) 
    { 
      IJobDetail jobDetail = bundle.JobDetail; 
      Type jobType = jobDetail.JobType; 
      var job = (IJob)_container.GetInstance(jobType); 
      return new LifetimeScopeJobDecorator(job, _container); 
    } 

    public void ReturnJob(IJob job) 
    { 
    } 
} 

然後你可以初始化您的自定義石英容器

public static class QuartzScheduler 
{ 
    private static Container _quartzContainer { get; set; } 

    private static void Initialize() 
    { 
     Container container = new Container(); 
     container.RegisterLifetimeScope<IUnitOfWork, SqlUnitOfWork>(); 
     container.Register<ILogger, NLogLogger>(); 


     //To enable lifetime scoping, please make sure the EnableLifetimeScoping extension method is called during the configuration of the container. 
     container.EnableLifetimeScoping(); 
     container.Verify(); 
     _quartzContainer = new Container(); 

     var schedulerFactory = new StdSchedulerFactory(); 
     _quartzContainer.RegisterSingle<IJobFactory>(() => new SimpleInjectorJobFactory(container)); 
     _quartzContainer.RegisterSingle<ISchedulerFactory>(schedulerFactory); 
     _quartzContainer.Register<IScheduler>(() => 
     { 
      var scheduler = schedulerFactory.GetScheduler(); 
      scheduler.JobFactory = _quartzContainer.GetInstance<IJobFactory>(); 
      return scheduler; 
     } 
     ); 
     _quartzContainer.Verify(); 

啓動調度

public static void StartJobs() 
    { 
     Initialize(); 

      //Ask the scheduler factory for a scheduler 
      IScheduler scheduler = _quartzContainer.GetInstance<IScheduler>(); 
      scheduler.Start(); 

    } 
3

我有與Castle.Windsor和Quartz.Net相同的問題。我發現的唯一適用方法是ScopedLifetime,但您必須自己控制範圍。如果有新的請求進入,打開一個作用域,所有的服務都將在這個作用域(所謂的UnitOfWork;)中被解析,當請求結束時,關閉它。

作業處理有點棘手。但是你有兩種方法可以解決這個問題。對於這兩種方式,您需要一個可以啓動示波器的工廠。

  1. 你的工作得到在構造一個工廠和Execute(IJobExecutionContext context)工廠啓動範圍,解決了您的服務(資料庫...)進行什麼工作做,並關閉範圍。 A using(Factory.BeginScope())適用於此。這樣做的缺點是,由於使用服務定位器模式,因此被認爲是不好的做法。

    public class MyJob 
    { 
        private readonly Factory Factory; 
    
        public MyJob(Factory factory) 
        { 
         Factory = factory; 
        } 
    
        public void Execute(IJobExecutionContext context) 
        { 
         using (Factory.BeginScope()) 
         { 
          var repo = Factory.Create<IFooRepository>(); 
          // Do stuff 
    
          Factory.Release(repo); 
         } 
        } 
    } 
    
  2. 你的工作得到了工廠或東西,可以啓動一個範圍和服務像功能:Func<IFooRepository> repositoryFunc。然後在你的Execute方法中,啓動作用域(再次使用)並調用你的repository,它會返回你在該作用域中的真實存儲庫,你可以像你想要的那樣使用它。這應該是最好的方法。請注意,這不被視爲服務定位器模式,因爲您爲該服務提供了Func<>工作,而您只需控制範圍。

    public class MyJob 
    { 
        private readonly Factory Factory; 
        private readonly Func<IFooRepository> RepositoryFunc; 
    
        public MyJob(Factory factory, Func<IFooRepository> repositoryFunc) 
        { 
         Factory = factory; 
         RepositoryFunc= repositoryFunc; 
        } 
    
        public void Execute(IJobExecutionContext context) 
        { 
         using (Factory.BeginScope()) 
         { 
          var repo = RepositoryFunc(); 
          // Do Stuff 
         } 
        } 
    } 
    

的問題

  1. PerThreadLifetimeManager

    的操作,因爲的DbContext已經設置無法完成。

    這是因爲Quartz使用了一個MainThread,並且默認情況下是一個帶有10個線程的ThreadPool。所有作業都在MainThread中創建,然後在池中的一個空閒線程中執行。如果你開始一個工作,DBContext綁定到MainThread。當你開始另一個工作時,那麼已經有一個綁定到這個線程的DBContext,不管它是處置還是關閉,並且LifeTimeManager將解決這個已經使用的上下文。 如果您是第一次啓動Job,則線程是新的,並且您當前的DBContext綁定到此線程。當你開始下一個工作並且它在同一個線程中執行時,總是有一個綁定到這個線程的DBContext。 LifeTimeManager解決了這個已經使用的上下文,但是你不能使用它,因爲它關閉了。

  2. PerResolveLifeTimeManager

    一個實體對象不能被IEntityChangeTracker

    這個問題來自EF的多個實例引用。即使您使用相同的構造函數解析不同的服務,您解決的每個服務都會獲得一個新的作用域。這會導致您使用的每個Repository都有自己的DBContext。而EF禁止使用具有相同實體的不同DBContext。

+0

感謝您的解釋。我還沒有完全掌握如何實施這家工廠,但我明天就會着手。如果你有任何的例子,那就太好了。是否可以在我的UnityJobFactory中打開和關閉這些範圍?這樣我就可以讓工作對工廠無知了。 – Andre

0

請看看Quartz.Unity NuGet包https://github.com/hbiarge/Quartz.Unity,這種統一包裝有一個體面的實施ScopedLifetime的。

除了上面的nuget包,如果您使用多個統一容器實例並將lifetimemanager作爲委託進行傳遞,它將允許您正確處理一次性類型,例如每個石英作業的DBContext以及每個http請求。

你必須爲asp設置一個單獨的IUnityContainer實例。net mvc/web api和另一個用於Quartz調度器的IUnityContainer實例。

這是一個完整的工作示例 https://github.com/vinodres/DITestingWithQuartz

如果你看一下QuartzStartup.cs,我用它來初始化Quartz調度。爲了簡單起見,我們假設IHelloService是一次性類型,它必須放置在每個作業的末尾以及每個http請求的末尾。在這裏,我創建了一個單獨的實例 IUnityContainer分配給QuartzContainer,並從Quartz.Unity nuget包中添加了一個名爲QuartzUnityExtention的新擴展。還調用了我在名爲unityconfig.cs的另一個文件內創建的.Configure擴展方法。該方法將Func作爲參數。該參數允許您根據執行路徑傳遞不同的生命期管理器實例。

這裏是QuartzStartup.cs

[assembly: OwinStartup(typeof(DiTestingApp.QuartzStartup))] 
namespace DiTestingApp 
{ 
    /// <summary> 
    /// 
    /// </summary> 
    public class QuartzStartup 
    { 
     private static readonly ILog Log = LogManager.GetLogger(typeof(QuartzStartup)); 
     /// <summary> 
     /// Get the hangfire container. 
     /// </summary> 
     private static readonly Lazy<IUnityContainer> QuartzContainer = new Lazy<IUnityContainer>(() => 
     { 
      var container = new UnityContainer(); 
      container.AddNewExtension<QuartzUnityExtension>(); 
      container.Configure(() => new HierarchicalLifetimeManager()); 
      return container; 
     }); 

     /// <summary> 
     /// 
     /// </summary> 
     /// <param name="app"></param> 
     public void Configuration(IAppBuilder app) 
     { 
      Log.Info("Quartz Startup Intitializing..."); 
      var container = QuartzContainer.Value; 
      InitScheduler(container); 
      Log.Info("Quartz Startup Intialization Complete..."); 

      var properties = new AppProperties(app.Properties); 
      var cancellationToken = properties.OnAppDisposing; 
      if (cancellationToken != CancellationToken.None) 
      { 
       cancellationToken.Register(() => 
       { 
        QuartzContainer.Value.Dispose(); 
        Log.Info("Quartz container disposed (app pool shutdown)."); 
       }); 
      } 
     } 

     private void InitScheduler(IUnityContainer container) 
     { 
      try 
      { 
       var scheduler = container.Resolve<IScheduler>(); 
       scheduler.Start(); 

       IJobDetail job = JobBuilder.Create<HelloWorldJob>().Build(); 

       ITrigger trigger = TriggerBuilder.Create() 
        .WithSimpleSchedule(x => x.WithIntervalInSeconds(20).RepeatForever()) 
        .Build(); 

       scheduler.ScheduleJob(job, trigger); 
      } 
      catch (Exception ex) 
      { 
       Log.Error(ex); 
      } 

     } 
    } 
} 

甲類似的設置我有asp.net MVC /網頁API依賴解析器配置。我創建了一個名爲UnityMvcActivator.cs的文件,當我調用.Configure擴展方法時,我傳遞了PerRequestLifetimeManager。

UnityMvcActivator.cs

using System; 
using System.Linq; 
using System.Web.Http; 
using System.Web.Mvc; 
using Common.Logging; 
using Microsoft.Practices.Unity; 
using Microsoft.Practices.Unity.Mvc; 

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(DiTestingApp.App_Start.UnityWebActivator), "Start")] 
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(DiTestingApp.App_Start.UnityWebActivator), "Shutdown")] 

namespace DiTestingApp.App_Start 
{ 
    /// <summary>Provides the bootstrapping for integrating Unity with ASP.NET MVC.</summary> 
    public static class UnityWebActivator 
    { 
     private static readonly ILog Log = LogManager.GetLogger(typeof(UnityWebActivator)); 
     /// <summary> 
     /// Get the hangfire container. 
     /// </summary> 
     private static readonly Lazy<IUnityContainer> WebContainer = new Lazy<IUnityContainer>(() => 
     { 
      var container = new UnityContainer(); 
      container.Configure(() => new PerRequestLifetimeManager()); 
      return container; 
     }); 

     /// <summary>Integrates Unity when the application starts.</summary> 
     public static void Start() 
     { 
      Log.Info("Web api DI container intializing."); 
      var container = WebContainer.Value; 

      FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First()); 
      FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container)); 

      DependencyResolver.SetResolver(new UnityDependencyResolver(container)); 

      // TODO: Uncomment if you want to use PerRequestLifetimeManager 
      Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule)); 

      var resolver = new Microsoft.Practices.Unity.WebApi.UnityDependencyResolver(container); 

      GlobalConfiguration.Configuration.DependencyResolver = resolver; 
      Log.Info("Web api DI container intialization complete."); 
     } 

     /// <summary>Disposes the Unity container when the application is shut down.</summary> 
     public static void Shutdown() 
     { 
      Log.Info("Web api DI container disposing."); 
      var container = WebContainer.Value; 
      container.Dispose(); 
     } 
    } 
} 

既然說到這裏你IUnityContainer註冊類型的部分。這裏是UnityConfig.cs中配置方法的實現。在這裏,我已經註冊IHelloService以使用disposableLifetimeManager委託。調用委託時,根據執行路徑提供適當的生命期管理器。如果IHelloService在asp.net mvc/web api的上下文中使用,它將是PerRequestLifetimeManager。如果它在Quartz Job中使用,它將是HierarchicalLifetimeManager。

UnityConfig.cs

using System; 
using DiTestingApp.Models; 
using Microsoft.Practices.Unity; 
using Quartz; 
using Testing.Scheduler; 

namespace DiTestingApp 
{ 
    /// <summary> 
    /// Specifies the Unity configuration for the main container. 
    /// </summary> 
    public static class UnityConfig 
    { 
     /// <summary> 
     /// 
     /// </summary> 
     /// <param name="container"></param> 
     /// <param name="disposableLifetimeManager"></param> 
     /// <returns></returns> 
     public static IUnityContainer Configure(this IUnityContainer container, Func<LifetimeManager> disposableLifetimeManager) 
     { 

      container.RegisterType<IJob, HelloWorldJob>(); 
      container.RegisterType<IHelloService, HelloService>(disposableLifetimeManager()); 
      return container; 
     } 
    } 
} 

HierarchicalLifetimeManager用於石英執行路徑,因此任何一次性類型將被適當地設置在每個作業結束。

如果Quartz.Unity實現不足以滿足您的用例,您可以隨時對其進行進一步定製。

相關問題