4

私はasp.NET MVCアプリケーションでQuartz.Netを使用しようとしています。 UnityとしてDIを使用しています(PerRequestLifeTimeManager)。Quartz.netのUnity LifeTimeマネージャ

しかし、Quartz.Netは、PerRequestLifeTimeManagerとはうまく動作しません。なぜなら、まずはリクエストがないからです。それを解決しようとする依存関係はnullを返します。

は、私は次のように状況に応じて2つのライフタイムのマネージャーを使用するために、アダプタのようなクラスを作成しました:私はPerThreadLifetimeManagerを試してみた

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(); 
    } 
} 

、それはその後、最初に、後続の罰金を実行しますメッセージで実行が失敗する

DbContextが に設定されているため、操作を完了できません。エンティティオブジェクトは、私の仕事はと同様に、非常に簡単です IEntityChangeTracker

の複数のインスタンスによって参照することができない私はPerResolveLifeTimeManagerに変更しようとしたが、それは

で失敗

以下:

[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) 
    { 

    } 
} 

Quartzジョブの依存関係の寿命を正しく管理するにはどうすればよいですか?

答えて

1

これは、シンプルインジェクターではじめて使用できます。 これは、しかしクォーツの旧バージョンとあったあなたは

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) 
    { 
    } 
} 

その後、あなたの仕事の工場にあなたを呼び出すカスタム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 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でしたが、自分でScopeを制御する必要があります。新しいリクエストが来たら、スコープを開き、すべてのサービスがこのスコープ(いわゆるUnitOfWork;)で解決されます。リクエストが終了したら、それを閉じます。

ジョブの処理はやや難解です。しかし、あなたはそれを解決する2つの方法があります。どちらの方法でも、スコープを開始できるファクトリが必要です。

  1. あなたの仕事は、コンストラクタで工場を取得し、Execute(IJobExecutionContext context)に工場が(...リポジトリ)あなたのサービスを解決し、スコープを開始し、ジョブがないとスコープを閉じ何でも実行します。 using(Factory.BeginScope())はこれに最適です。これの欠点は、サービスロケータパターンを使用するために悪い習慣とみなされます。 Func<IFooRepository> repositoryFunc

    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. あなたの仕事のような関数としてスコープとあなたのサービスを開始することができます工場か何かを取得します。次に、Executeメソッドで、スコープを(再びusingを使用して)起動し、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 
         } 
        } 
    } 
    

DbContextが配置されているため、操作を完了することができないという問題

  1. PerThreadLifetimeManager

    これは、QuartzがMainThreadを使用し、デフォルトで10スレッドのThreadPoolを使用するためです。すべてのジョブはMainThreadで作成され、プールから空きスレッドで実行されます。ジョブを開始すると、DBContextはMainThreadにバインドされます。別のジョブを開始すると、すでにこのスレッドにバインドされているDBContextが存在します.DropContextはすでに破棄されていても閉じられていても、LifeTimeManagerはこれがすでに使用されているコンテキストを解決します。 初めてジョブを開始すると、スレッドは新規であり、現在のDBContextはこのスレッドにバインドされています。次のジョブを開始して同じスレッドで実行すると、常にこのスレッドにバインドされたDBContextが存在します。 LifeTimeManagerはこれまでに使用されていたコンテキストを解決しますが、閉じられているため使用できません。

  2. PerResolveLifeTimeManager

    エンティティオブジェクトは、この問題は、EFから来IEntityChangeTracker

    の複数のインスタンスによって参照することはできません。解決した各サービスは、同じコンストラクターで異なるサービスを解決した場合でも、新しいスコープを取得します。これにより、使用するすべてのリポジトリに独自のDBContextが作成されます。また、EFは同じエンティティで異なるDBContextを使用することを禁じます。

+0

ありがとうございます。私はまだこの工場をどのように実装するかを完全には把握していませんが、私は明日それについて作業します。例があれば素晴らしいでしょう。 UnityJobFactory内のスコープを開いたり閉じたりすることは可能ですか?このように私は仕事を工場に無知にすることができました。 – Andre

0

https://github.com/hbiarge/Quartz.Unityをパッケージnuget Quartz.Unityを見てください、この団結パッケージはScopedLifetimeのまともな実装があります。

上記のナゲットパッケージに加えて、複数のユニティコンテナインスタンスを使用し、ライフメーションマネージャをデリゲートとして渡すと、各クォーツジョブの各DBContextやhttpごとの使い捨てタイプを適切に処理できます要求。

aspの別のIUnityContainerインスタンスをセットアップする必要があります。net mvc/web apiと、Quartzスケジューラ用のIUnityContainerの別のインスタンス。

は、ここでは、QuartzStartup.csを見れば、私はクォーツスケジューラを初期化するためにそれを使用している完全に動作するサンプル https://github.com/vinodres/DITestingWithQuartz

です。わかりやすくするために、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内にconfigureメソッドの実装を示します。ここでは、IHelloServiceを登録して、disposableLifetimeManagerデリゲートを使用しています。デリゲートが呼び出されると、実行パスに応じて適切なライフタイムマネージャが提供されます。 IHelloServiceをasp.net mvc/web apiのコンテキストで使用すると、PerRequestLifetimeManagerになります。また、Quartz Job内でHierarchicalLifetimeManagerが使用されている場合、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実装では、使用例では不十分な場合は、いつでもカスタマイズできます。