2017-04-25 2 views
1

그래서 제가 유지하고있는 일부 레거시 코드에 버그가 있습니다. 약간의 데이터 손상이 발생하므로 다소 심각한 문제입니다. 근본 원인을 찾아 냈으며 신뢰할 수있는 샘플 응용 프로그램을 만들었습니다. 가능한 한 기존 응용 프로그램에 거의 영향을주지 않으면 서 문제를 해결하고 싶습니다. 그러나 저는 고심하고 있습니다.DI를 사용하여 레거시 코드의 NHibernate 세션에 인터셉터를 추가하십시오.

버그는 데이터 액세스 계층에 있습니다. 더 구체적으로, 새로운 Nhibernate 세션에 인터셉터가 주입되는 방법. 인터셉터는 저장 또는 플러시 할 때 특정 엔터티 속성을 설정하는 데 사용됩니다. LoggedInPersonID 속성은 거의 모든 엔티티에서 찾을 수 있습니다. 모든 엔터티는 데이터베이스 스키마를 사용하여 CodeSmith 템플릿에서 생성되므로 LoggedInPersonID 속성은 데이터베이스의 거의 모든 테이블에있는 열에 해당합니다. 몇 가지 다른 열과 트리거와 함께 데이터베이스에서 레코드를 작성하고 수정 한 사용자를 추적하는 데 사용됩니다. 데이터를 삽입하거나 업데이트하는 모든 트랜잭션은 LoggedInPersonID 값을 제공해야합니다. 그렇지 않으면 트랜잭션이 실패합니다.

클라이언트가 새 세션을 요구할 때마다 SessionFactory에서 OpenSession이 호출됩니다 (Nhibernate의 SessionFactory가 아니라 래퍼). 인터셉터는 BeforeInit 이벤트를 통해 주입

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

: 아래의 코드는 SessionFactory에 래퍼 클래스의 관련 부분을 도시한다.

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

이 버그는 우리의 웹 서비스 (WCF의 SOAP)에서 특히 눈에 띄는 : 아래

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

BeforeInit 이벤트 핸들러를 등록하는 응용 프로그램에서 사용하는 도우미 클래스입니다 : 아래 인터셉터 구현은 . 웹 서비스 엔드 포인트 바인딩은 모두 basicHttpBinding입니다. 새로운 Nhibernate 세션이 각 클라이언트 요청에 대해 생성됩니다. LoggedInPersonIDInterceptorUtil.Setup 메서드는 클라이언트가 인증 된 후 호출되며 인증 된 클라이언트의 ID가 클로저에 캡처됩니다. 그런 다음 다른 클라이언트 요청하기 전에 에 전화는 SessionFactory.openSession을 트리거 코드에 도달하는 경주는 다른 폐쇄와 BeforeInit 이벤트에 이벤트 핸들러가 레지스터의 - , 그것은 BeforeInit 이벤트의 호출의 마지막 핸들러가 있기 때문에 잠재적으로 잘못된 요격기를 반환하는 "승리"목록 버그는 일반적으로 두 클라이언트가 거의 동시에 요청을 수행 할 때 발생하지만, 두 클라이언트가 다른 실행 시간 (인증에서 OpenSession까지 다른 세션보다 길어짐)으로 다른 웹 서비스 메소드를 호출하는 경우에도 발생합니다.

데이터 손상 외에도 이벤트 핸들러가 등록 취소되지 않았으므로 메모리 누수가 발생합니까? 웹 서비스 프로세스가 하루에 한 번 이상 재활용되는 이유는 무엇입니까?

실제로는 BeforeInit (및 AfterInit)과 같은 이벤트가 필요합니다. I OpenSession 메서드의 서명을 변경하고 IInterceptor 매개 변수를 추가 할 수 있습니다. 하지만 이것은 코드의을 깨뜨릴 것이고, 세션이 검색 될 때마다 인터셉터를 전달하고 싶지는 않습니다. 나는 이것을 투명하게하고 싶습니다. 인터셉터는 DAL을 사용하는 모든 응용 프로그램에서 교차 관심사이므로 종속성 주입이 가능한 솔루션입니까? Unity는 응용 프로그램의 다른 영역에서 사용됩니다.

오른쪽 방향의 상관 찔러 크게 이해 될 것이다 :

답변

2

대신 각 ISessionFactory.OpenSession 호에서 인터셉터를 공급하기 때문에, I는 세계적으로 구성된 단일 인스턴스 인터셉터 (Configuration.SetInterceptor())를 사용한다.

이 인스턴스는 응용 프로그램에 적합한 모든 요청/사용자/당이 데이터를 격리 할 수 ​​있도록 적절한 컨텍스트에서 사용할 데이터를 검색합니다. (System.ServiceModel.OperationContext, System.Web.HttpContext, ..., 응용 프로그램의 종류에 따라 다릅니다.)

LoggedInPersonIDInterceptorUtil.Setup이 현재라고 어디 귀하의 경우 상황에 맞는 데이터가 설정됩니다.

다른 컨텍스트를 필요로하는 응용 프로그램에 대해 동일한 인터셉터 구현을 사용해야하는 경우 추가 할 구성 매개 변수에 따라 사용할 컨텍스트를 선택해야합니다 (또는 인터셉터에 종속성으로 삽입해야 함).

+0

유망한 접근 방법입니다. 아이디어를 가져 주셔서 감사합니다. 내일 시험해 보시고, 이것이 작동한다면 답변으로 표시하겠습니다 :) – matsho

관련 문제