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