NHibernate и ADO.NET Connection Pooling

Похоже, что 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().List();
                 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().List();
                 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 также не сработает, поскольку 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);
            }
        }
    }

9
задан Community 23 May 2017 в 12:25
поделиться