2017-04-25 2 views
1

Es gibt also einen Fehler in einem älteren Code, den ich erhalte. Es verursacht einige leichte Datenkorruption, also ist es ziemlich ernst. Ich habe die Ursache gefunden und eine Beispielanwendung erstellt, die den Fehler zuverlässig reproduziert. Ich möchte es mit so wenig Auswirkungen auf bestehende Anwendungen wie möglich beheben, aber ich kämpfe.Verwenden von DI zum Hinzufügen von Interceptor zu NHibernate-Sitzungen in altem Code

Der Fehler liegt in der Datenzugriffsebene. Genauer gesagt, wie ein Interzeptor in eine neue Nhibernate-Sitzung injiziert wird. Der Interzeptor wird verwendet, um beim Speichern oder Löschen eine bestimmte Entitätseigenschaft festzulegen. Die Eigenschaft LoggedInPersonID wird in fast allen Entitäten gefunden. Alle Entitäten werden mithilfe des Datenbankschemas aus CodeSmith-Vorlagen generiert, sodass die LoggedInPersonID-Eigenschaft einer Spalte entspricht, die in fast allen Tabellen in der Datenbank gefunden wird. Zusammen mit ein paar anderen Spalten und Triggern wird damit verfolgt, welcher Benutzer einen Datensatz in der Datenbank erstellt und geändert hat. Jede Transaktion, die Daten einfügt oder aktualisiert, muss einen LoggedInPersonID-Wert bereitstellen, da andernfalls die Transaktion fehlschlägt.

Wenn ein Client eine neue Sitzung benötigt, wird OpenSession in der SessionFactory aufgerufen (nicht Nhibernates SessionFactory, sondern ein Wrapper). Der folgende Code zeigt die relevanten Teile der Session Wrapper-Klasse:

public class SessionFactory 
{ 
    private ISessionFactory sessionFactory; 

    private SessionFactory() 
    { 
     Init(); 
    } 

    public static SessionFactory Instance 
    { 
     get 
     { 
      return Nested.SessionFactory; 
     } 
    } 

    private static readonly object _lock = new object(); 

    public ISession OpenSession() 
    { 
     lock (_lock) 
     { 
      var beforeInitEventArgs = new SessionFactoryOpenSessionEventArgs(null); 

      if (BeforeInit != null) 
      { 
       BeforeInit(this, beforeInitEventArgs); 
      } 

      ISession session; 

      if (beforeInitEventArgs.Interceptor != null 
       && beforeInitEventArgs.Interceptor is IInterceptor) 
      { 
       session = sessionFactory.OpenSession(beforeInitEventArgs.Interceptor); 
      } 
      else 
      { 
       session = sessionFactory.OpenSession(); 
      } 

      return session; 
     } 
    } 

    private void Init() 
    { 
     try 
     { 
      var configuration = new Configuration().Configure(); 
      OnSessionFactoryConfiguring(configuration); 
      sessionFactory = configuration.BuildSessionFactory(); 
     } 
     catch (Exception ex) 
     { 
      Console.Error.WriteLine(ex.Message); 
      while (ex.InnerException != null) 
      { 
       Console.Error.WriteLine(ex.Message); 
       ex = ex.InnerException; 
      } 
      throw; 
     } 
    } 

    private void OnSessionFactoryConfiguring(Configuration configuration) 
    { 
     if(SessionFactoryConfiguring != null) 
     { 
      SessionFactoryConfiguring(this, new SessionFactoryConfiguringEventArgs(configuration)); 
     } 
    } 

    public static event EventHandler<SessionFactoryOpenSessionEventArgs> BeforeInit; 
    public static event EventHandler<SessionFactoryOpenSessionEventArgs> AfterInit; 
    public static event EventHandler<SessionFactoryConfiguringEventArgs> SessionFactoryConfiguring; 

    public class SessionFactoryConfiguringEventArgs : EventArgs 
    { 
     public Configuration Configuration { get; private set; } 

     public SessionFactoryConfiguringEventArgs(Configuration configuration) 
     { 
      Configuration = configuration; 
     } 
    } 

    public class SessionFactoryOpenSessionEventArgs : EventArgs 
    { 

     private NHibernate.ISession session; 

     public SessionFactoryOpenSessionEventArgs(NHibernate.ISession session) 
     { 
      this.session = session; 
     } 

     public NHibernate.ISession Session 
     { 
      get 
      { 
       return this.session; 
      } 
     } 

     public NHibernate.IInterceptor Interceptor 
     { 
      get; 
      set; 
     } 
    } 

    /// <summary> 
    /// Assists with ensuring thread-safe, lazy singleton 
    /// </summary> 
    private class Nested 
    { 
     internal static readonly SessionFactory SessionFactory; 

     static Nested() 
     { 
      try 
      { 
       SessionFactory = new SessionFactory(); 
      } 
      catch (Exception ex) 
      { 
       Console.Error.WriteLine(ex); 
       throw; 
      } 
     } 
    } 
} 

Der Interceptor durch die BeforeInit Ereignis eingespritzt wird. Unten ist die Implementierung Interceptor:

public class LoggedInPersonIDInterceptor : NHibernate.EmptyInterceptor 
{ 
    private int? loggedInPersonID 
    { 
     get 
     { 
      return this.loggedInPersonIDProvider(); 
     } 
    } 

    private Func<int?> loggedInPersonIDProvider; 

    public LoggedInPersonIDInterceptor(Func<int?> loggedInPersonIDProvider) 
    { 
     SetProvider(loggedInPersonIDProvider); 
    } 

    public void SetProvider(Func<int?> provider) 
    { 
     loggedInPersonIDProvider = provider; 
    } 

    public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState, 
             string[] propertyNames, NHibernate.Type.IType[] types) 
    { 
     return SetLoggedInPersonID(currentState, propertyNames); 
    } 

    public override bool OnSave(object entity, object id, object[] currentState, 
          string[] propertyNames, NHibernate.Type.IType[] types) 
    { 
     return SetLoggedInPersonID(currentState, propertyNames); 
    } 

    protected bool SetLoggedInPersonID(object[] currentState, string[] propertyNames) 
    { 
     int max = propertyNames.Length; 

     var lipid = loggedInPersonID; 

     for (int i = 0; i < max; i++) 
     { 
      if (propertyNames[i].ToLower() == "loggedinpersonid" && currentState[i] == null && lipid.HasValue) 
      { 
       currentState[i] = lipid; 

       return true; 
      } 
     } 

     return false; 
    } 
} 

Unten finden Sie eine Hilfsklasse von Anwendungen verwendet wird, eine BeforeInit Event-Handler registrieren: Besonders prominent

public static class LoggedInPersonIDInterceptorUtil 
    { 
     public static LoggedInPersonIDInterceptor Setup(Func<int?> loggedInPersonIDProvider) 
     { 
      var loggedInPersonIdInterceptor = new LoggedInPersonIDInterceptor(loggedInPersonIDProvider); 

      ShipRepDAL.ShipRepDAO.SessionFactory.BeforeInit += (s, args) => 
      {  
       args.Interceptor = loggedInPersonIdInterceptor; 
      }; 

      return loggedInPersonIdInterceptor; 
     } 
    } 
} 

Der Fehler in unserem Web-Services (WCF SOAP) . Die Webdienst-Endpunktbindungen sind alle basicHttpBinding. Für jede Clientanforderung wird eine neue Nhibernate-Sitzung erstellt. Die LoggedInPersonIDInterceptorUtil.Setup-Methode wird aufgerufen, nachdem ein Client authentifiziert wurde und die authentifizierte Client-ID im Abschluss erfasst wurde. Dann gibt es noch ein Rennen Code zu erreichen, die einen Aufruf zu SessionFactory.OpenSession vor einer anderen Client-Anforderung löst ein Event-Handler zum BeforeInit Ereignisse mit einem anderen Verschluss - denn, es ist der letzte Handler in der BeforeInit Veranstaltung Aufruf Liste, die "gewinnt" und möglicherweise den falschen Interceptor zurückgibt. Der Fehler tritt normalerweise auf, wenn zwei Clients fast gleichzeitig Anfragen stellen, aber auch wenn zwei Clients unterschiedliche Web-Service-Methoden mit unterschiedlichen Ausführungszeiten aufrufen (eine dauert länger von der Authentifizierung bis OpenSession als eine andere).

Zusätzlich zur Datenbeschädigung gibt es auch einen Speicherverlust, da die Ereignishandler nicht de-registriert sind? Vielleicht ist dies der Grund, warum unser Web-Service-Prozess mindestens einmal am Tag recycelt wird?

Es sieht wirklich wie die BeforeInit (und AfterInit) Ereignisse gehen müssen. I könnte die Signatur der OpenSession-Methode ändern und einen IInterceptor-Parameter hinzufügen. Aber das würde viel Code brechen, und ich möchte nicht in einem Interceptor übergeben, wenn eine Sitzung abgerufen wird - ich möchte, dass dies transparent ist. Da der Interceptor bei allen Anwendungen, die den DAL verwenden, ein Querschnittsthema darstellt, wäre die Abhängigkeitsinjektion eine praktikable Lösung? In einigen anderen Bereichen unserer Anwendungen wird Unity verwendet.

Jeder Schub in der richtigen Richtung sehr geschätzt :)

Antwort

2

Statt bei jedem Anruf die Abfangvorrichtung ISessionFactory.OpenSession das Zuführens würde, I global eine einzelne Instanz konfigurierte Interceptor verwenden würde (Configuration.SetInterceptor()).

Diese Instanz würde die zu verwendenden Daten aus einem geeigneten Kontext abrufen, um diese Daten pro Anfrage/Benutzer/für die jeweilige Anwendung zu isolieren. (System.ServiceModel.OperationContext, System.Web.HttpContext, ..., je nach Anwendung Art.)

Die Kontextdaten in Ihrem Fall eingestellt werden würden, wo LoggedInPersonIDInterceptorUtil.Setup zur Zeit genannt wird.

Wenn Sie die gleiche Interceptor-Implementierung für Anwendungen benötigen, die unterschiedliche Kontexte erfordern, müssen Sie den zu verwendenden Kontext entsprechend einem Konfigurationsparameter auswählen, den Sie hinzufügen würden (oder ihn als Abhängigkeit in Ihren Interceptor injizieren).

+0

Das klingt nach einem vielversprechenden Ansatz! Danke für die Idee, ich werde es morgen testen und das als Antwort markieren, wenn es funktioniert :) – matsho

Verwandte Themen