2011-09-27 163 views
9

看来,NHibernate不集中ADO.NET数据库连接。连接只在事务提交或回滚时关闭。对源代码的回顾显示,没有办法配置NHibernate,以便在ISession处置时关闭连接。NHibernate和ADO.NET连接池

这种行为的意图是什么? ADO.NET具有连接池本身。没有必要在交易中一直保持开放。有了这种行为也是不必要的分布式事务创建。因此,http://davybrion.com/blog/2010/05/avoiding-leaking-connections-with-nhibernate-and-transactionscope/中描述的一种可能的解决方法不起作用(至少不适用于NHibernate 3.1.0)。我正在使用Informix。每个其他数据库似乎存在同样的问题(NHibernate Connection Pooling)。

是否有避免此问题的其他解决方法或建议?

这里有一个单元测试重现问题:

[Test] 
    public void DoesNotCloseConnection() 
    { 
    using (SessionFactoryCache sessionFactoryCache = new SessionFactoryCache()) 
    { 
     using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = TimeSpan.FromMinutes(10) })) 
     { 
      fixture.Setup(); // Creates test data 

      System.Data.IDbConnection connectionOne; 
      System.Data.IDbConnection connectionTwo; 

      using (ISessionFactory sessionFactory = sessionFactoryCache.CreateFactory(GetType(), new TestNHibernateConfigurator())) 
      { 
       using (ISession session = sessionFactory.OpenSession()) 
       { 
       var result = session.QueryOver<Library>().List<Library>(); 
       connectionOne = session.Connection; 
       } 
      } 

      // At this point the first IDbConnection used internally by NHibernate should be closed 

      using (ISessionFactory sessionFactory = sessionFactoryCache.CreateFactory(GetType(), new TestNHibernateConfigurator())) 
      { 
       using (ISession session = sessionFactory.OpenSession()) 
       { 
       var result = session.QueryOver<Library>().List<Library>(); 
       connectionTwo = session.Connection; 
       } 
      } 

      // At this point the second IDbConnection used internally by NHibernate should be closed 

      // Now two connections are open because the transaction is still running 
      Assert.That(connectionOne.State, Is.EqualTo(System.Data.ConnectionState.Closed)); // Fails because State is still 'Open' 
      Assert.That(connectionTwo.State, Is.EqualTo(System.Data.ConnectionState.Closed)); // Fails because State is still 'Open' 
     } 
    } 
    } 

NHibernate的会话的载放什么也不做,因为我们仍然在交易

SessionImpl.cs:

public void Dispose() 
    { 
     using (new SessionIdLoggingContext(SessionId)) 
     { 
      log.Debug(string.Format("[session-id={0}] running ISession.Dispose()", SessionId)); 
      if (TransactionContext!=null) 
      { 
       TransactionContext.ShouldCloseSessionOnDistributedTransactionCompleted = true; 
       return; 
      } 
      Dispose(true); 
     } 
    } 

注入自定义ConnectionProvider也不起作用,因为ConnectionManager调用ConnectionProvider有几个先决条件,检查是否关闭了一个连接交易不被允许。

ConnectionManager.cs:

public IDbConnection Disconnect() { 
     if (IsInActiveTransaction) 
      throw new InvalidOperationException("Disconnect cannot be called while a transaction is in progress."); 

     try 
     { 
      if (!ownConnection) 
      { 
       return DisconnectSuppliedConnection(); 
      } 
      else 
      { 
       DisconnectOwnConnection(); 
       ownConnection = false; 
       return null; 
      } 
     } 
     finally 
     { 
      // Ensure that AfterTransactionCompletion gets called since 
      // it takes care of the locks and cache. 
      if (!IsInActiveTransaction) 
      { 
       // We don't know the state of the transaction 
       session.AfterTransactionCompletion(false, null); 
      } 
     } 
    } 
+0

据我所知,为了利用事务,数据库需要相同的连接。所以我不觉得奇怪的是,只要事务正在运行,它就会保持连接正常运行?如果连接返回到池中,则不会确保您第二次从池中接收到相同的连接。 – jishi

+0

但是,在您的具体测试中,您正在检查基础IdbConnection,我认为它是ADO.NET的一部分,并且不是您在此情况下测试的ADO.NET连接池吗?你应该做的是创建两个不同的会话(从同一工厂,以确保是这种情况),并确保您收到相同的连接。 – jishi

+0

在每个事务开始时,Driver类(在我的情况下是OdbcDriver)创建一个新的DbConnection(OdbcConnection)。这个连接保持打开整个事务是不必要的。我写的测试实际上使用了一个SessionFactory中的两个不同的会话。 – Antineutrino

回答

8

NHibernate的有两种 “模式”。

  • 您可以在应用程序中打开连接,然后由应用程序来管理它。这个“模式”在传递连接到sessionfactory.OpenSession(connection)时使用。
  • 或者连接是由NH创建的。然后在会议结束时关闭。这种“模式”在未通过连接时使用sessionfactory.OpenSession()

TransactionScope有一些支持。它最有可能使用第一个“模式”。可能连接不是由NH承担,而是由交易范围来承担。我不清楚,我不使用环境交易。

NH 顺便使用了ADO.NET连接池。

您也可以使用ISession.Disconnect()断开会话并使用ISession.Reconnect()重新连接。

documentation你会发现:

的方法ISession.Disconnect()将断开 ADO.NET连接的会话,并返回到池中(除非你 提供的连接)的连接。

+1

感谢您的回复,但我认为您对“第二”模式有误。当会话被处理时,连接保持打开状态。正如你在上面发布的代码片断(ConnectionManager.cs)中看到的那样,不可能在事务范围内断开/重新连接。在您提供的链接中,还表示您必须先提交/中止事务,然后才能断开/重新连接。连接(IDbConnection)由ConnectionManager类作为私有成员控制,而不是由事务控制。 – Antineutrino

0

您可以通过将以下设置添加到连接字符串来完成此操作。

Pooling=true; 
Min Pool Size=3; 
Max Pool Size=25; 
Connection Lifetime=7200; 
Connection Timeout=15; 
Incr Pool Size=3; 
Decr Pool Size=5; 

池:启用池为您的应用程序

敏游泳池:最小连接数时,所有会话被关闭甚至保持开放。

最大池:应用程序将打开到数据库的最大连接数。当达到最大值时,它将等待连接超时指定的秒数,然后抛出异常。

连接超时:最大时间(以秒为单位)等待从池中的空闲连接

连接生存期:当一个连接被返回到池时,将其创建时间与当前时间进行比较,并如果该时间跨度(以秒为单位)超过由连接生存期指定的值,连接将被销毁。值为零(0)会导致池连接的最大连接超时。

Incr池大小:控制使用所有连接时建立的连接数。

Decr池大小:控制未使用过量已建立的连接时关闭的连接数。

http://www.codeproject.com/Articles/17768/ADO-NET-Connection-Pooling-at-a-Glance