.net SqlConnection не закрывается даже при использовании {} - PullRequest
16 голосов
/ 06 ноября 2008

Пожалуйста, помогите!

Справочная информация

У меня есть приложение WPF, которое обращается к базе данных SQL Server 2005. База данных работает локально на компьютере, на котором запущено приложение.

Везде, где я использую Linq DataContext, я использую оператор using {} и передаю результат функции, которая возвращает объект SqlConnection, который был открыт и для которого был выполнен SqlCommand, прежде чем вернуться в конструктор DataContext. Т.е.

// In the application code
using (DataContext db = new DataContext(GetConnection()))
{
    ... Code 
}

где getConnection выглядит следующим образом (я убрал «пух» из функции, чтобы сделать его более читабельным, но нет никаких дополнительных функциональных возможностей, которые отсутствуют).

// Function which gets an opened connection which is given back to the DataContext constructor
public static System.Data.SqlClient.SqlConnection GetConnection()
{
   System.Data.SqlClient.SqlConnection Conn = new System.Data.SqlClient.SqlConnection(/* The connection string */);

    if ( Conn != null )
    {
        try
        {
            Conn.Open();
        }
        catch (System.Data.SqlClient.SqlException SDSCSEx)
        {
             /* Error Handling */
        }

        using (System.Data.SqlClient.SqlCommand SetCmd = new System.Data.SqlClient.SqlCommand())
        {
            SetCmd.Connection = Conn;
            SetCmd.CommandType = System.Data.CommandType.Text;

            string CurrentUserID = System.String.Empty;
            SetCmd.CommandText = "DECLARE @B VARBINARY(36); SET @B = CAST('" + CurrentUserID + "' AS VARBINARY(36)); SET CONTEXT_INFO @B";

            try
            {
                SetCmd.ExecuteNonQuery();
            }
            catch (System.Exception)
            {
                /* Error Handling */
            }
        }

        return Conn;
    }

Я не думаю, что приложение, являющееся WPF, имеет какое-либо отношение к моей проблеме.

У меня проблема

Несмотря на то, что SqlConnection располагается вместе с DataContext в Sql Server Management Studio, я все еще могу видеть множество открытых соединений с:

status : 'Sleeping' 
command : 'AWAITING COMMAND' 
last SQL Transact Command Batch : DECLARE @B VARBINARY(36); SET @B = CAST('GUID' AS VARBINARY(36)); SET CONTEXT_INFO @B

В конечном итоге пул соединений израсходован, и приложение не может продолжить работу.

Таким образом, я могу только заключить, что запуск SQLCommand для установки Context_Info означает, что соединение не удаляется при удалении DataContext.

Может ли кто-нибудь заметить что-либо очевидное, что помешало бы закрытию и удалению соединений при удалении DataContext, которым они используются?

Ответы [ 6 ]

19 голосов
/ 06 ноября 2008

С MSDN (DataContext Constructor (IDbConnection)):

Если вы предоставляете открытое соединение, DataContext не закроет его. Поэтому не создавайте DataContext с открытым соединением если у вас нет веских причин делать это.

По сути, похоже, что ваши соединения ждут, пока GC завершит их, прежде чем они будут выпущены. Если у вас есть много кода, который делает это, один из подходов может состоять в том, чтобы переопределить Dispose() в частичном классе контекста данных и закрыть соединение - просто обязательно документируйте, что контекст данных предполагает владение соединением!

    protected override void Dispose(bool disposing)
    {
        if(disposing && this.Connection != null && this.Connection.State == ConnectionState.Open)
        {
            this.Connection.Close();
            this.Connection.Dispose();
        }
        base.Dispose(disposing);
    }

Лично я бы с радостью предоставил ему (обычный контекст данных, без хакера выше) открытое соединение, пока я «использовал» соединение (позволяя мне выполнять несколько операций) - т.е.

using(var conn = GetConnection())
{
   // snip: some stuff involving conn

   using(var ctx = new FooContext(conn))
   {
       // snip: some stuff involving ctx
   }

   // snip: some more stuff involving conn
}
7 голосов
/ 06 ноября 2008

SqlProvider, используемый LINQ DataContext, закрывает соединение SQL (через SqlConnectionManager.DisposeConnection), только если оно было тем, которое открыло его. Если вы дадите уже открытый объект SqlConnection конструктору DataContext, он не закроет его для вас. Таким образом, вы должны написать:

using (SqlConnection conn = GetConnection())
using (DataContext db = new DataContext(conn))
{
    ... Code 
}
3 голосов
/ 31 марта 2009

Я столкнулся с той же проблемой при использовании Entity Framework. Мой ObjectContext был обернут вокруг блока using.

Соединение было установлено, когда я позвонил SaveChanges(), но после того, как оператор using вышел из области видимости, я заметил, что в SQL Management Studio все еще есть "AWAITING COMMAND" для .NET SQL Client. Похоже, это связано с поведением поставщика ADO.NET, у которого по умолчанию включен пул соединений.

С " Использование пула соединений с SQL Server " на MSDN (выделено мной):

Пул соединений уменьшает количество раз, когда новые соединения должны быть открыты. Пулер поддерживает право собственности на физическое соединение. Он управляет соединениями, поддерживая набор активных соединений для каждой данной конфигурации соединения. Всякий раз, когда пользователь вызывает Open для соединения, диспетчер проверяет, есть ли доступное соединение в пуле. Если пул соединения доступен, он возвращает его вызывающей стороне, а не открывает новое соединение. Когда приложение вызывает Close для соединения, диспетчер возвращает его в пул набор активных соединений вместо того, чтобы фактически закрывать его. Как только соединение возвращается в пул, оно готово для повторного использования при следующем Open вызове.

Также ClearAllPools и ClearPool представляется полезным для явного закрытия всех соединений в пуле, если это необходимо.

1 голос
/ 06 ноября 2008

Что ж, спасибо за помощь, ребята, теперь это решено ..

По сути, я взял элементы большинства ответов выше и реализовал конструктор DataContext, как указано выше (я уже перегружал конструкторы, поэтому это не было большим изменением).

// Variable for storing the connection passed to the constructor
private System.Data.SqlClient.SqlConnection _Connection;

public DataContext(System.Data.SqlClient.SqlConnection Connection) : base(Connection)
{
    // Only set the reference if the connection is Valid and Open during construction
    if (Connection != null)
    {
        if (Connection.State == System.Data.ConnectionState.Open)
        {
            _Connection = Connection;                    
        }
    }           
}

protected override void Dispose(bool disposing)
{        
    // Only try closing the connection if it was opened during construction    
    if (_Connection!= null)
    {
        _Connection.Close();
        _Connection.Dispose();
    }

    base.Dispose(disposing);
}

Причина, по которой это делается, а не некоторые из приведенных выше советов, заключается в том, что при обращении к this.Connection в методе dispose выдается ObjectDisposedException .

И вышесказанное работает так, как я надеялся!

1 голос
/ 06 ноября 2008

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

Решение:

Создайте свой собственный класс DataContext, производный от автоматически сгенерированного. (переименуйте базовый, чтобы вам не приходилось менять какой-либо другой код).

В вашем производном DataContext - добавьте функцию Dispose (). В этом - распоряжаться внутренней связью.

0 голосов
/ 06 ноября 2008

Dispose должен закрыть соединения, как указывает MSDN :

Если SqlConnection выходит из сфера, она не будет закрыта. Следовательно, Вы должны явно закрыть соединение по телефону Закрыть или Dispose. Закрыть и утилизировать функционально эквивалентный. Если значение пула соединений установить в true или да, основной соединение возвращается к пул подключений. С другой стороны, если Пул установлен на ложь или нет, базовое соединение с сервером закрыты.

Полагаю, ваша проблема связана с GetContext().

...