【发布时间】:2011-09-27 09:52:48
【问题描述】:
NHibernate 似乎没有对 ADO.NET 数据库连接进行池化。只有在事务提交或回滚时才会关闭连接。对源代码的审查表明,无法配置 NHibernate 以便在释放 ISession 时关闭连接。
这种行为的意图是什么? 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-Session 什么都不做,因为我们仍在事务中
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 也将不起作用,因为调用 ConnectionProvider 的 ConnectionManager 有几个前提条件,检查不允许在事务中关闭连接。
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);
}
}
}
【问题讨论】:
-
据我所知,数据库需要相同的连接才能使用事务。因此,只要事务正在运行,它就保持连接处于活动状态,我并不觉得奇怪?如果连接返回到池中,则无法确保您第二次从池中收到相同的连接。
-
但是,在您的具体测试中,您正在检查底层 IdbConnection,我认为它是 ADO.NET 的一部分,而在这种情况下您正在测试的不是 ADO.NET 连接池?你应该做的是创建两个不同的会话(同样来自同一个工厂,确保是这种情况)并确保你收到相同的连接。
-
在每个事务开始时,Driver 类(在我的例子中是 OdbcDriver)创建一个新的 DbConnection (OdbcConnection)。此连接保持打开整个事务,这是不必要的。我编写的测试实际上使用了来自一个 SessionFactory 的两个不同的会话。
-
我仍然不确定检查 .State 是否是公平的测试。查看msdn.microsoft.com/en-us/library/… 它说:“在打开的连接上调用 State 属性会增加应用程序开销,因为每个这样的调用都会导致对底层 ODBC 驱动程序的 SQL_ATTR_CONNECTION_DEAD 调用以确定连接是否仍然有效。”这表明“已关闭”连接可能是死连接,因此不可重用于池。
-
进一步:msdn.microsoft.com/en-us/library/… 表示以下内容:“当对连接执行最近的网络操作时,指示 SqlConnection 的状态。”这使我认为,在这种情况下,状态实际上并不代表连接的当前状态,并且不能用于确保连接对池可用。池也可以是一个驱动特性,我知道 Npgsql 已经内置了连接池,并且不依赖 ADO.NET。
标签: .net nhibernate ado.net connection-pooling