2011-09-27 80 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