4

現在、NinjectでTopShelfを使用してWindowsサービスを作成しています。私はセットアップに次のコードをWindowsサービスはTopShelf使用していますTopShelf、Ninject、EFコードの問題点の問題

public class NinjectDependencyResolver : NinjectModule 
{ 
    public override void Load() 
    { 
     Settings settings = CreateSettings(); 
     ConnectionStringSettings connectionStringSettings = ConfigurationManager.ConnectionStrings["DB"]; 

     Bind<IDatabaseFactory>().To<DatabaseFactory>() 
           .InThreadScope() 
           .WithConstructorArgument("connectionString", connectionStringSettings.Name); 

     Bind<IUnitOfWork>().To<UnitOfWork>(); 
     Bind<IMyRepository>().To<MyRepository>(); 
     Bind<IExternalReportService>().To<ReportService1>(); 
     Bind<IExternalReportService>().To<ReportService2>(); 
     Bind<IExternalDataService>().To<DataService1>(); 
     Bind<IExternalDataService>().To<DataService2>(); 

     Bind<Settings>().ToConstant(settings); 
    } 

    private Settings CreateSettings() 
    { 
     // Reads values from app.config and returns object with settings 
    } 
} 
:これらはNinjectバインディングです

public class BotService 
{ 
    private readonly Timer timer; 

    public BotService(double interval) 
    { 
     this.timer = new Timer(interval) { AutoReset = true }; 
     this.timer.Elapsed += (sender, eventArgs) => Run(); 
    } 

    public void Start() 
    { 
     this.timer.Start(); 
    } 

    public void Stop() 
    { 
     this.timer.Stop(); 
    } 

    private void Run() 
    { 
     IKernel kernel = new StandardKernel(new NinjectDependencyResolver()); 

     Settings settings = kernel.Get<Settings>(); 

     if (settings.Service.ServiceType == 1) 
     { 
      // The interface implementation has constructor injection of IUnitOfWork and IMyRepository 
      kernel.GetAll<IExternalReportService>().Each(x => x.Update()); 
     } 

     if (settings.Service.ServiceType == 2) 
     { 
      // The interface implementation has constructor injection of IUnitOfWork and IMyRepository 
      kernel.GetAll<IExternalDataService>().Each(x => x.GetData()); 
     } 

     kernel.Get<IUnitOfWork>().Dispose(); 
     kernel.Dispose(); 
    } 
} 

static void Main(string[] args) 
{ 
    using (IKernel kernel = new StandardKernel(new NinjectDependencyResolver())) 
    { 
     Settings settings = kernel.Get<Settings>(); 

     var host = HostFactory.New(x => 
     { 
      x.Service<BotService>(s => 
      { 
       s.ConstructUsing(name => new BotService(settings.Service.TimeInterval)); 
       s.WhenStarted(ms => ms.Start()); 
       s.WhenStopped(ms => ms.Stop()); 
      }); 

      x.RunAsNetworkService(); 

      x.SetServiceName(settings.Service.ServiceName); 
      x.SetDisplayName(settings.Service.DisplayName); 
      x.SetDescription(settings.Service.Description); 
     }); 

     host.Run(); 
    } 
} 

これは、Windowsサービスの背後にあるオブジェクトは、すべての仕事をしています

まず、このコードに満足していないと言いましょう。アプリケーションが起動すると、カーネルのインスタンスが作成され、設定の値がフェッチされ、TopShelfを使用してBotServiceオブジェクトを使用してWindowsサービスを作成します。

タイマイベントが発生するたびに、Run()メソッドが実行されます。ここでカーネルの別のインスタンスが作成され、再び設定を読み込み、カーネルがそのインタフェースのすべての実装をフェッチし、対応するメソッドを実行します。これらの各実装には、IUnitOfWorkとIMyRepositoryがデータアクセスのために注入されるコンストラクタがあります。

このメソッドが終了すると、コンテキストを破棄してカーネルを破棄します。

なぜこのように設定しましたか?もともと、私はMainに1つのカーネルしか作成せず、BotServiceにコンストラクタを使用して、カーネルの別のインスタンスを作成するのではなく、実装を注入しました。問題は、DatabaseFactoryが動作するためにInSingletonScopeまたはInThreadScopeが必要であることでした。

InSingeltonScopeを使用した場合、コンテキストが古くなり、最終的にはコンテキストが無効な場所ではじまり始める可能性があります。 InThreadScopeを使用した場合、スレッドが完了するとオブジェクトを破棄しないため、同じ問題が発生します。最終的にRun()は以前に使用されたスレッドを使用していましたが、すでにContextを処分しているので例外が発生しています。コンテキストをうまく処分するコード行を削除した場合、InSingletonScopeと同じ問題が発生し、スレッドが再利用されたときに古くなったコンテキストになります。

これは、各Time Run()が実行されることが保証されている現在のコードにつながります。コンテキストは、処理される場所まであり、カーネルも同様に廃棄されるので、カーネルが再作成されて以来、私たちは新しいコンテキストを取得しています(少なくとも、これは起こっていると思います)。

私のNinjectのスキルはそれほど高度ではなく、この問題に近づく方法に関する情報は非常に限られています。私は適切なアプローチは、メインのみで1つのカーネルを作成し、必要なものをコンストラクタ経由でBotServiceオブジェクトに注入できるようにすることだと思います。しかし同時に、このアプローチで上記のスコープの1つを使用した場合に起こる古いコンテキストを避けるために、Run()ごとにコンテキストを作成する必要があります。

上記の例を修正するにはどうしたらよいですか?私は現在、Ninject 2.2.1.4を使用しています。

+0

私はTopshelfメーリングリストでこれを投げました。 https://groups.google.com/d/topic/topshelf-discuss/5H_VUYNzjeY/discussionここで大きな助けになるには、NInjectには馴染みがありません。もし私が推測しなければならないのであれば、ループの始めにUnitOfWork/etcをリフレッシュして最後に処分して、新しいカーネルを作成する必要はありません。 – Travis

答えて

3

まず、問題を少し解明してみましょう。カスタムスコープが必要な依存関係(DatabaseFactory)(または、他の人がそれらを参照する可能性があるような生涯)があるように聞こえます。あなたは、DatabaseFactoryの同じインスタンスがRunの1回の実行の間返されるように思えます。

:ファイル名を指定して実行を実行するたびにリフレッシュされて、あなたはすべてのインスタンスを気にしない場合は

  1. :これが正しい場合

    は、私はあなたが次のいずれかの方法でこれを実現することができるはずだと思います

    private StandardKernel _kernel /* passed into constructor */; 
    
    public void Run() 
    { 
        using (var block = _kernel.BeginBlock()) 
        { 
         var settings = block.Get<Settings>(); 
         if (settings.Service.ServiceType == 1) 
         { 
          // The interface implementation has constructor injection of IUnitOfWork and IMyRepository 
          block.GetAll<IExternalReportService>().Each(x => x.Update()); 
         } 
    
         if (settings.Service.ServiceType == 2) 
         { 
          // The interface implementation has constructor injection of IUnitOfWork and IMyRepository 
          block.GetAll<IExternalDataService>().Each(x => x.GetData()); 
         } 
        } 
    } 
    
  2. 実行するたびに特定のインスタンスをリフレッシュする場合は、にする必要があります。これは、カスタムスコープオブジェクト(InScope()メソッドとthis post from Nateを参照)を使用して行うことができます。残念ながら、Timerは別のスレッドの実行が完了する前にRunを呼び出す可能性があるので、多分マルチスレッドの問題に遭遇するでしょう。
+0

ええと、あなたはおそらくブロックの外に設定のフェッチを残すことができます。 – Mario

+0

マリオ、大変感謝しています。だから、ソリューションナンバー1では、MainからBotServiceコンストラクタにカーネルを渡すと言っています(現在のtimeintervalで渡す方法)?あなたのソリューションでは、コードの中のブロックを使用することで、それを正しく処理できるので、コンテキストやカーネルを処分する必要はありません。 – Thomas

+0

usingブロックは、 'block'インスタンスでDisposeを呼び出す処理を行います。 Ninjectは、IDisposableを実装するすべてのインスタンスで自動的にDisposeを呼び出します。 IUnitOfWorkで使用されているインスタンスがIDisposableを実装している限り、Ninjectはインスタンスがスコープから外れたときにDisposeを呼び出す処理を行います。 – Mario