私が達成しようとしているのは、私のWebサイトがメッセージを生成してバスに載せ、サービスがそれを受け取り、行のAddedBy/UpdatedByフィールドに自動的にデータを入力する監査でデータベースに書き込むことです.
これを行うには、ASP.Net アプリケーションにログインしているユーザーからの Thread.CurrentPrincipal からのメッセージ ヘッダーにユーザー ID を書き込む NServiceBus IMessageMutator コンポーネントを使用します。私のサービスでは、IMessageModule を使用してこのヘッダーを抽出し、これを Thread.CurrentPrincipal にバインドします。これはうまく機能し、メッセージ ハンドラー中に、Thread.CurrentPrincipal.Identity.Name が、Web アプリケーションでメッセージを生成したユーザー ID に正しくバインドされていることがわかります。
NHibernate の IPreUpdateEventListener/IPreInsertEventListener を利用して、DB に書き込まれる前に各エンティティの AddedBy/UpdatedBy を設定しています。これは Web サイトでは完全に機能しますが、私の NServiceBus サービスでは、リスナーが実行されるスレッドは、ハンドラーが実行されるスレッドとは異なります。つまり、スレッドの CurrentPrincipal は、IMessageModule でバインドされた ID ではなくなりました。
NHibernate がコール スタックで DistributedTransactionFactory を使用していることがわかります。これが問題の原因であると思われます。コミットが失敗した場合、メッセージが再試行されたり、エラー キューに入れられたり、キューからのメッセージの削除が失敗したり、更新が DB にロールバックされたりしないなど、トランザクション性を失いたくありません。
私は Web を見回しましたが、すべての例でスレッドの CurrentPrincipal を利用して、行を変更したユーザーの ID をバインドしています。私が探しているのは、NHibernate リスナーをメッセージ ハンドラーと同じスレッドに保持するか、ユーザー ID をリスナーに渡して、DB に書き込まれる前にエンティティにバインドできるようにする方法です。
これが私のリスナーです。そこにある Set メソッドを省略しました
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;
}
}
ID を抽出して現在のスレッドの ID にバインドする NServiceBus Message モジュールを次に示します。
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() { }
}