2016-07-06 24 views
0

Was ich versuche zu erreichen, ist meine Website löst eine Nachricht und legt es auf den Bus, ein Service nimmt es auf und schreibt in die Datenbank mit Auditing, die das AddedBy/UpdatedBy Feld automatisch füllt der Reihe.NServiceBus und NHibernate EventListeners laufen auf verschiedenen Threads

Ich mache dies mithilfe der NServiceBus IMessageMutator-Komponente, die die Benutzer-ID in die Nachrichtenheader von Thread.CurrentPrincipal schreibt, die von dem angemeldeten Benutzer in meiner ASP.Net-Anwendung stammt. In meinem Dienst verwende ich ein IMessageModule, um diesen Header zu extrahieren und an das Thread.CurrentPrincipal zu binden. Das funktioniert großartig und während meines Message-Handlers kann ich sehen, dass Thread.CurrentPrincipal.Identity.Name korrekt an die Benutzer-ID gebunden ist, die die Nachricht in der Webanwendung ausgelöst hat.

Mit dem IPreUpdateEventListener/IPreInsertEventListener von NHibernate setze ich die AddedBy/UpdatedBy jeder Entity, bevor es in die DB geschrieben wird. Das funktioniert auf der Website perfekt, aber in meinem NServiceBus-Dienst unterscheidet sich der Thread, auf dem der Listener ausgeführt wird, von dem Thread, auf dem der Handler ausgeführt wurde, was bedeutet, dass das CurrentPrincipal des Threads nicht mehr die in meinem IMessageModul gebundene ID ist.

Ich kann sehen, NHibernate verwendet eine DistributedTransactionFactory in der Aufrufliste, die ich vermute, ist die Ursache für mein Problem. Ich möchte keine Transaktionalität verlieren, so dass bei fehlgeschlagenem Commit die Nachricht nicht erneut versucht oder in die Fehlerwarteschlange eingereiht wird und wenn das Entfernen der Nachricht aus der Warteschlange fehlschlägt und das Update nicht zur DB zurückwechselt.

Ich habe mich im Internet umgesehen und alle Beispiele verwenden das CurrentPrincipal des Threads, um die ID des Benutzers zu binden, der die Zeilen geändert hat. Was ich suche, ist eine Möglichkeit, den NHibernate-Listener entweder im selben Thread wie den Nachrichtenhandler zu belassen oder die Benutzer-ID an den Listener zu übergeben, damit er an die Entität gebunden werden kann, bevor sie in die Datenbank geschrieben wird.

Hier meine Zuhörer ist, habe ich die Set-Methode darin

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

gefunden weggelassen Und hier ist das NServiceBus Nachrichtenmodul, das die ID extrahiert und bindet es an die Identität des aktuellen Thread

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

Morgan. Ich habe Probleme, Ihre Anforderung vollständig zu verstehen. Hast du nächste Woche Zeit für einen Skype-Anruf? ich bin "simon.cropp" auf Skype – Simon

+0

Hallo Simon, ja bitte. Ich werde Ihnen eine E-Mail schicken, um eine Zeit zu vereinbaren. – Morgan

Antwort

0

Danke Simon für all deine Hilfe. Nachdem ich mein Problem ausführlich besprochen und diskutiert habe, wie NServiceBus intern arbeitet, habe ich Ihren Einblick genommen und ein Einheitsarbeitsmodul für NServiceBus implementiert.

Wir haben uns auf die Transaktion verlassen, die pro Nachricht erstellt wurde, um unsere NHibernate-Sitzung an die Datenbank zu übergeben. Dies geschieht auf dem verteilten Transaktionscontroller (speziell geschieht dies hier NHibernate.Transaction.AdoNetWithDistributedTransactionFactory.DistributedTransactionContext), der einen Thread-Pool nutzt.

Mithilfe der Schnittstelle IManageUnitsOfWork von NServiceBus konnte ich unsere Transaktion im selben Thread wie der Nachrichtenhandler explizit committen, wie unten im Codebeispiel.

Als eine Randnotiz für zukünftige Leser ist die beste Lösung hier, Thread.CurrentPrincipal nicht zu verwenden, da diese Lösung in Multi-Thread-Umgebungen fehlschlägt, wie es für mich ist.

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