2011-09-27 30 views
9

Parece que NHibernate no agrupa las conexiones de bases de datos ADO.NET. Las conexiones solo se cierran cuando la transacción se confirma o revierte. Una revisión del código fuente muestra que no hay forma de configurar NHibernate para que cierre las conexiones cuando se descarta la ISession.NHibernate y ADO.NET Connection Pooling

¿Cuál fue la intención de este comportamiento? ADO.NET tiene una agrupación de conexiones. No es necesario mantenerlos abiertos todo el tiempo dentro de la transacción. Con este comportamiento también se crean transacciones distribuidas innecesariamente. Por lo tanto, una posible solución descrita en http://davybrion.com/blog/2010/05/avoiding-leaking-connections-with-nhibernate-and-transactionscope/ no funciona (al menos no con NHibernate 3.1.0). Estoy usando Informix. El mismo problema parece existir para cualquier otra base de datos (NHibernate Connection Pooling).

¿Hay alguna otra solución o consejo para evitar este problema?

Aquí es una prueba de unidad que reproduce el problema:

[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' 
     } 
    } 
    } 

La Eliminación del NHibernate-Session no hace nada, ya que estamos todavía en una transacción

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

La inyección de un ConnectionProvider personalizado tampoco funcionará, ya que el ConnectionManager que llama al ConnectionProvider tiene varias condiciones previas que verifican el cierre de una conexión dentro de un transacción no está permitida

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

Por lo que yo sé, las bases de datos necesitan la misma conexión con el fin de utilizar una transacción. ¿Entonces no me parece extraño que mantenga activa una conexión mientras se ejecuta una transacción? Si la conexión se devuelve al grupo, no hay nada que garantice que recibirá la misma conexión del grupo la segunda vez. – jishi

+0

Sin embargo, en su prueba concreta, está comprobando la IdbConnection subyacente, que supongo es parte de ADO.NET, y ¿no es la agrupación de conexiones ADO.NET que está probando en ese caso? Lo que debe hacer es crear dos sesiones diferentes (también desde la misma fábrica, asegúrese de que sea el caso) y asegúrese de recibir la misma conexión. – jishi

+0

Al comienzo de cada transacción, la clase Driver (en mi caso, OdbcDriver) crea una nueva DbConnection (OdbcConnection). Esta conexión permanece abierta toda la transacción que es innecesaria. La prueba que he escrito realmente usa dos sesiones diferentes de una SessionFactory. – Antineutrino

Respuesta

8

NHibernate tiene dos "modos".

  • O abra la conexión en su aplicación, entonces le corresponde a la aplicación para administrarlo. Este "modo" se usa al pasar una conexión a sessionfactory.OpenSession(connection).
  • O la conexión ha sido creada por NH. Luego se cierra cuando la sesión se cierra. Este "modo" se utiliza cuando no está pasando una conexión a sessionfactory.OpenSession()

Hay algún apoyo para TransactionScope. Es muy probable que esté usando el primer "modo". Probablemente NH no mantenga la conexión, sino el alcance de la transacción. No sé exactamente, no uso transacciones de entorno.

NH es usando el conjunto de conexiones ADO.NET por cierto.

También puede desconectar la sesión usando ISession.Disconnect() y reconectar usando ISession.Reconnect().

En el documentation a encontrar:

El método ISession.Disconnect() desconectará la sesión de la conexión ADO.NET y devolver la conexión a la piscina (a menos que ha proporcionado la conexión).

+1

Gracias por su respuesta, pero creo que se equivoca con el "segundo" modo.Cuando se elimina la sesión, la conexión permanece abierta. Como se puede ver en el código snipped he publicado anteriormente (ConnectionManager.cs) no es posible desconectar/reconectar dentro de un ámbito de transacción. En el enlace que proporcionó también se dice que primero debe confirmar/abortar la transacción antes de que pueda desconectarse/reconectarse nuevamente. La conexión (IDbConnection) está controlada por la clase ConnectionManager como un miembro privado y no por la transacción. – Antineutrino

0

Puede lograr esto agregando las siguientes configuraciones a su cadena de conexión.

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

Pooling: activa la puesta en común para su aplicación

Min piscina: El número mínimo de conexiones para mantener abierta incluso cuando todas las sesiones se cierran.

Grupo máximo: el número máximo de conexiones que la aplicación abrirá en la base de datos. Cuando se alcanza el máximo, esperará el número de segundos especificado por Tiempo de espera de conexión y luego emitirá una excepción.

Tiempo de espera de conexión: Tiempo máximo (en segundos) para esperar una conexión libre de la piscina

por vida

conexión: Cuando una conexión es devuelta a la piscina, es el tiempo de creación se compara con la hora actual, y la la conexión se destruye si ese lapso de tiempo (en segundos) excede el valor especificado por la vida útil de la conexión. Un valor de cero (0) hace que las conexiones agrupadas tengan el máximo tiempo de espera de conexión.

Incr Pool Size: controla el número de conexiones que se establecen cuando se utilizan todas las conexiones.

Tamaño del grupo de servidores: controla el número de conexiones que se cierran cuando no se utiliza una cantidad excesiva de conexiones establecidas.

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