Утечка соединения в C# DataBase.ExecuteScalar

Следующий метод в статическом классе дает мне исключение тайм-аута, потому что пул соединений исчерпан.

Находясь в режиме отладки, я заглянул в студию управления sql и увидел, что там 150 спящих процессов.

Я ожидал, что соединения будут автоматически закрыты... Я также пытался использовать статический член и все равно получил ту же ошибку.

Есть идеи? вот код:

public static Decimal ExecuteScalarDec(string procName, params object[] parameters)
{
    try
    {
        return (Decimal)DatabaseFactory.CreateDatabase().ExecuteScalar(procName, parameters);
    }
    catch (Exception ex)
    {
        throw new Exception(procName.ToString() + " " + parameters.ToString(), ex);
    }
}

«По замыслу большинство методов класса базы данных обрабатывают открытие и закрытие соединений с базой данных при каждом вызове. Поэтому код приложения не должен включать код для управления соединениями». ExecuteReader является исключением (поскольку он возвращает ресурс). ExecuteScalar находится в подвешенном состоянии: он возвращает «скаляр». Однако я думаю, что скаляр может быть довольно тяжелым, например. поток, созданный из возврата большого типа данных, и для этого потребуется сохранить соединение открытым. — Ремус Русану

Я не мог прокомментировать ваш ответ, потому что он говорит: «Для комментирования требуется 50 репутации». После того, как я зарегистрировал своего пользователя...

Я возвращаю идентификатор столбца в executeScalar(), и значение возвращается - я знаю это, потому что следующий вызов для выполнения скаляра вызывается только после того, как я получаю значение... Не имеет смысла, что поток будет оставаться открытым навсегда И я увидел в sql Management, что все процессы спят.


person Matt    schedule 23.07.2009    source источник
comment
Что делает метод ExecuteScalar() с базовым объектом DbConnection — вызывает ли он Dispose() или Close(), например, с помощью оператора using?   -  person Mark Cidade    schedule 23.07.2009


Ответы (1)


public static Decimal ExecuteScalarDec(string procName, params object[] parameters)
{
    try
    {
        using (Database database = DatabaseFactory.CreateDatabase())
        {
            return (Decimal)database.ExecuteScalar(procName, parameters);
        }
    }
    catch (Exception ex)
    {
        throw new Exception(procName.ToString() + " " + parameters.ToString(), ex);
    }
}

Обновить

Хорошо, так как это код EnterpriseLibrary. Класс Database реализует ExecuetScalar следующим образом (другие подписи в конце концов рухнет до этого):

 public virtual object ExecuteScalar(DbCommand command)
        {
            if (command == null) throw new ArgumentNullException("command");

            using (ConnectionWrapper wrapper = GetOpenConnection())
            {
                PrepareCommand(command, wrapper.Connection);
                return DoExecuteScalar(command);
            }
        }

и ConnectionWrapper удаляет соединение (конец исходного файла в ссылке), поэтому, по теории, ваш вызов должен быть в порядке и удалять соединение.

Метод GetOpenConnection() возвращает оболочку, которая удаляет соединение... за исключением случаев, когда оно существует в текущем TransactionScopeConnections:

 protected ConnectionWrapper GetOpenConnection(bool disposeInnerConnection)
    {
        DbConnection connection = TransactionScopeConnections.GetConnection(this);
        if (connection != null)
        {
            return new ConnectionWrapper(connection, false);
        }

        return new ConnectionWrapper(GetNewOpenConnection(), disposeInnerConnection);
    }

А вот как TransactionScopeConnections возвращает соединение:

  public static DbConnection GetConnection(Database db)
    {
        Transaction currentTransaction = Transaction.Current;

        if (currentTransaction == null)
            return null;

        Dictionary<string, DbConnection> connectionList;
        DbConnection connection;

        lock (transactionConnections)
        {
            if (!transactionConnections.TryGetValue(currentTransaction, out connectionList))
            {
                // We don't have a list for this transaction, so create a new one
                connectionList = new Dictionary<string, DbConnection>();
                transactionConnections.Add(currentTransaction, connectionList);

                // We need to know when this previously unknown transaction is completed too
                currentTransaction.TransactionCompleted += OnTransactionCompleted;
            }
        }

        lock (connectionList)
        {
            // Next we'll see if there is already a connection. If not, we'll create a new connection and add it
            // to the transaction's list of connections.
            // This collection should only be modified by the thread where the transaction scope was created
            // while the transaction scope is active.
            // However there's no documentation to confirm this, so we err on the safe side and lock.
            if (!connectionList.TryGetValue(db.ConnectionString, out connection))
            {
                // we're betting the cost of acquiring a new finer-grained lock is less than 
                // that of opening a new connection, and besides this allows threads to work in parallel
                connection = db.GetNewOpenConnection();
                connectionList.Add(db.ConnectionString, connection);
            }
        }

        return connection;
    }

Теперь, если я не ошибаюсь, TransactionsScopeConnections всегда будет создавать новую связь для нового объекта базы данных (как в вашем случае) и хранить их во внутреннем словаре. Объект базы данных не реализует Disposable, поэтому я теряюсь в определении того, кто именно должен очищать соединения из этого TransactionScopeConnecitons внутреннего списка.

Мэтт, можно ли выполнить действия, описанные в этой статье об утечках CLR и посмотреть, есть ли в вашем процессе большое количество объектов базы данных? Загрузите SOS и сделайте !dumpheap -type Microsoft.Practices.EnterpriseLibrary.Data.Database. Если вы найдете много объектов, можете ли вы проследить стек контактов на некоторых из них с помощью !gcroot <AddressOfObject>

person Remus Rusanu    schedule 23.07.2009
comment
тогда вам лучше сделать его одноразовым, иначе вы будете отслеживать утечку соединений с сегодняшнего дня и до следующего дня. - person Remus Rusanu; 23.07.2009
comment
Тогда это вина вашего класса базы данных: если он не одноразовый, он должен убедиться, что он удаляет соединение в своем методе ExecuteScalar. - person Jon Skeet; 23.07.2009
comment
@Remus: Он не обязательно должен быть одноразовым, если он предназначен для одноразового класса ... он может заставить ExecuteScalar открыть соединение, выполнить и закрыть. Похоже, что это не так :( В принципе, если класс базы данных имеет ссылку на соединение с базой данных как на состояние, он должен реализовать IDisposable. - person Jon Skeet; 23.07.2009
comment
@Remus: К сожалению, на данный момент мы ничего не знаем о классе Database. @Matt: нам нужно больше информации. - person Jon Skeet; 23.07.2009
comment
Это EnterpriseLibrary, Мэтт? msdn.microsoft.com/en-us/library/dd203144.aspx - person Remus Rusanu; 23.07.2009
comment
По дизайну большинство методов класса Database обрабатывают открытие и закрытие соединений с базой данных при каждом вызове. Поэтому в код приложения не обязательно включать код для управления подключениями. ExecuteReader является исключением (поскольку он возвращает ресурс). ExecuteScalar находится в подвешенном состоянии: он возвращает «скаляр». Однако я думаю, что скаляр может быть довольно тяжелым, например. поток, созданный на основе возврата большого типа данных, который требует для поддержания соединения открытым. - person Remus Rusanu; 23.07.2009