2017-04-25 59 views
1

所以,有一些旧的代码,我保持一个错误。它会导致一些轻微的数据损坏,所以它相当严重。我找到了根本原因,并且制作了一个可靠的示例应用程序来重现该错误。我想尽可能减少对现有应用程序的影响,但我很挣扎。使用DI来拦截器在传统的代码添加到NHibernate的会议

的缺陷在于数据访问层英寸更具体地说,拦截器如何被注入到新的Nhibernate会话中。拦截器用于在保存或刷新时设置特定的实体属性。该属性LoggedInPersonID几乎可以在我们所有的实体上找到。所有实体使用的数据库架构的CodeSmith模板生成,所以LoggedInPersonID属性对应于被发现在数据库中几乎所有的表列。与其他一些列和触发器一起,它用于跟踪哪个用户在数据库中创建和修改记录。任何插入或更新数据的事务都需要提供LoggedInPersonID值,否则事务将失败。

每当客户端需要一个新的会话,就会调用的openSession在SessionFactory的(不是NHibernate的SessionFactory的,但包装)制成。下面的代码示出了会话工厂包装类的相关部分:

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

拦截通过BeforeInit事件注入。下面是拦截器实现:

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事件处理一个辅助类:

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)尤为突出。 Web服务端点的绑定都是basicHttpBinding的。为每个客户端请求创建一个新的Nhibernate会话。 LoggedInPersonIDInterceptorUtil.Setup方法在客户端通过身份验证后被调用,并在关闭中捕获经过身份验证的客户端ID。此外,还有以达到代码比赛之前另一个客户端请求触发到通话SessionFactory.OpenSession注册一个事件处理程序BeforeInit事件有不同的开合 - ,因为它在BeforeInit事件的调用的最后一个处理列表“获胜”,可能会返回错误的拦截器。的错误时,两个客户几乎同时提出请求,而且当两个客户都呼吁用不同的执行时间(一个来自认证比另一个需要更长时间的openSession)不同的Web服务方法通常发生。

除了数据损坏之外,还有内存泄漏,因为事件处理程序没有取消注册?这可能是我们的Web服务流程每天至少回收一次的原因?

这真的看起来像BeforeInit(和AfterInit)事件需要去。我可能会改变OpenSession方法的签名,并添加一个IInterceptor参数。但是这会打破很多的代码,并且我不想在检索会话时传入拦截器 - 我希望这是透明的。由于拦截器在所有使用DAL的应用程序中都是一个交叉问题,因此依赖注入是一个可行的解决方案吗?统一用于我们应用程序的其他一些领域。

在正确的方向的任何微移将不胜感激:)

回答

2

代替在每个ISessionFactory.OpenSession呼叫供给拦截,我会用一个拦截器实例全局配置(Configuration.SetInterceptor())。

此实例将从适当的上下文中检索要使用的数据,以便根据请求/用户/隔离此数据适合应用程序。 (​​,System.Web.HttpContext,...,根据应用的一种。)

你的情况上下文数据将被设置在那里LoggedInPersonIDInterceptorUtil.Setup当前调用。

如果您需要为需要不同的应用程序的应用程序使用相同的拦截器实现,那么您将需要根据要添加的某个配置参数选择要使用的上下文(或将其作为拦截器中的依赖项注入)。

+0

这听起来很有希望!谢谢你的想法,我会明天测试它,并将其标记为答案,如果它工作:) – matsho