2016-07-06 16 views
0

達成しようとしているのは、私のウェブサイトがメッセージを生成してバス上に置くと、サービスがそれを取り出して、AddedBy/UpdatedByフィールドを自動的に読み込む監査行の異なるスレッドで実行されているNServiceBusとNHibernate EventListeners

これは、ASP.NetアプリケーションにログインしているユーザーからのThread.CurrentPrincipalのメッセージヘッダーにユーザーIDを書き込むNServiceBus IMessageMutatorコンポーネントを使用して行います。 私のサービスでは、IMessageModuleを使用してこのヘッダを抽出し、これをThread.CurrentPrincipalにバインドします。これはうまく動作し、私のメッセージハンドラでは、Thread.CurrentPrincipal.Identity.NameがWebアプリケーションでメッセージを生成したユーザIDに正しくバインドされていることがわかります。

NHibernateのIPreUpdateEventListener/IPreInsertEventListenerを利用して、各エンティティのAddedBy/UpdatedByをDBに書き込む前に設定しています。これはウェブサイト上では完全に動作しますが、NServiceBusサービスでは、リスナーが実行されるスレッドはハンドラが実行されたスレッドとは異なります。つまり、スレッドのCurrentPrincipalはもはや自分のIMessageModuleにバインドされたIDではありません。

NHibernateが私の問題の原因と思われるコールスタックのDistributedTransactionFactoryを使用しているのがわかります。コミットが失敗した場合、メッセージは再試行されず、エラーキューに置かれず、キューからのメッセージの削除に失敗し、更新がDBにロールバックされないようなトランザクション性を失いたくありません。

私はWebを見渡しました。すべての例では、スレッドのCurrentPrincipalを使用して、行を変更したユーザーのIDをバインドしています。私が探しているのは、NHibernateリスナをメッセージハンドラと同じスレッドに保つか、リスナにユーザIDを渡して、DBに書き込まれる前にエンティティにバインドできる方法です。ここで

は私のリスナー、私はそれ

public class EntityPersistenceListener : IPreUpdateEventListener, IPreInsertEventListener 
{ 
    public bool OnPreUpdate(PreUpdateEvent @event) 
    { 
     var audit = @event.Entity as EntityBase; 
     if (audit == null) 
      return false; 

     var time = DateTimeFactory.GetDateTime(); 

     var name = Thread.CurrentPrincipal.Identity.Name; 

     Set(@event.Persister, @event.State, "AddedDate", audit.AddedDate); 
     Set(@event.Persister, @event.State, "AddedBy", audit.AddedBy); 
     Set(@event.Persister, @event.State, "UpdatedDate", time); 
     Set(@event.Persister, @event.State, "UpdatedBy", name); 

     audit.AddedDate = audit.AddedDate; 
     audit.AddedBy = audit.AddedBy; 
     audit.UpdatedDate= time; 
     audit.UpdatedBy = name; 

     return false;    
    } 
} 

で見つかったSetメソッドを省略し、ここでIDを抽出し、現在のスレッドのIDにバインドしNServiceBusメッセージモジュールでてきている

public class TenantAndInstanceInfoExtractor : IMessageModule 
{ 
    private readonly IBus _bus; 

    public TenantAndInstanceInfoExtractor(IBus bus) 
    { 
     _bus = bus; 
    } 

    public void HandleBeginMessage() 
    { 
     var headers = _bus.CurrentMessageContext.Headers; 

     if (headers.ContainsKey("TriggeredById")) 
      Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(headers["TriggeredById"]), null); 
     else 
      Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(string.Empty), null); 
    } 

    public void HandleEndMessage() 
    { 

    } 

    public void HandleError() { } 
} 
+1

モルガン。私はあなたの要件を完全に理解するのに苦労しています。来週のスカイプコールの時間はありますか?私はsimype.croppですskype – Simon

+0

こんにちはサイモン、はいしてください。私は時間を手配するためにあなたにメールします。 – Morgan

答えて

0

サイモンさん、ありがとうございました。私の問題を広範に巡って、NServiceBusが内部的にどのように動作するかを議論した後、私はあなたの洞察を得て、NServiceBusの作業単位モジュールを実装しました。

私たちは、NHibernateセッションをDBにコミットするメッセージごとに作成されたトランザクションに頼っていました。これは、分散トランザクションコントローラ(具体的にはここではNHibernate.Transaction.AdoNetWithDistributedTransactionFactory.DistributedTransactionContextで発生します)がスレッドプールを利用して行われます。

NServiceBusのIManageUnitsOfWorkインターフェイスを使用することで、以下のコード例のメッセージハンドラと同じスレッドでトランザクションを明示的にコミットできました。

将来の読者のための副メモとして、このソリューションはマルチスレッド環境では私のように失敗するため、ここで最善の解決策はThread.CurrentPrincipalを使用しないことです。

public class HiJumpNserviceBusUnitOfWork : IManageUnitsOfWork 
{ 
    private readonly IUnitOfWork _uow; 

    public HiJumpNserviceBusUnitOfWork(IUnitOfWork uow) 
    { 
     _uow = uow; 
    } 

    public void Begin() 
    { 
     _uow.ClearCache(); 
     _uow.BeginTransaction(); 
    } 

    public void End(Exception ex = null) 
    { 
     if (ex != null) 
     { 
      _uow.ClearCache(); 
     } 
     else 
     { 
      _uow.CommitTransaction(); 
     } 
    } 
} 
関連する問題