Использование заявления с connection.open

Я смотрел какой-то код и обсуждал его с коллегами.

В частности, раздел кода, который выглядит следующим образом.

    [Test]
    public void TestNormalWay()
    {
        using(var cn = GetConnection())
        {
            cn.Open();
            // do stuff
        }
    }

Возник вопрос:

"почему бы не переместить cn.Open в метод GetConnection."

Я сказал, что если "Открыть" выбрасывает исключение, вызов не будет вызван. Его ответ был

"Так что. Соединение не было открыто, так зачем ему нужно закрыто (или расположено)?"

Для меня это просто вопрос не желая знать, нужно ли мне или нет, чтобы я захотел распорядиться/закрыть, поэтому я бы повторил cn.Open в коде вместо того, чтобы переместить его в общую функцию.

НО, это интересно... поэтому я немного читал в Пул соединений SQL Server (ADO.NET)

Мне непонятно, существует ли сценарий, в котором вызывается cn.Open и он выдает и исключение, где dispose нужно будет вызывать.

Итак, в моем примере ниже есть какая-то разница между "TestNormalWay" и "WhyNotDoItThisWay"

    protected static DbConnection GetConnection()
    {
        DbConnection cn = new SqlConnection("SomeConnecitonstring... ");
        return cn; 
    }

    protected static DbConnection GetConnectionDangerousVersion()
    {
        DbConnection cn = new SqlConnection("SomeConnecitonstring... ");
        cn.Open();  // this will throw.. .dispose not called
        return cn; 
    }

    [Test]
    public void TestNormalWay()
    {
        using(var cn = GetConnection())
        {
            cn.Open();
            // do stuff
        }
    }

    [Test]
    public void WhyNotDoItThisWay()
    {
        using(var cn = GetConnectionDangerousVersion())
        {
            // do stuff
        }
    }

Ответы

Ответ 1

Как вы пишете свой код, вы всегда хотите открыть соединение, как только оно будет создано, поэтому нет никакой разницы.

Однако вы можете открывать и закрывать соединение несколько раз и в коде, предназначенном для этого, есть большая разница.

Мне может понадобиться написать код, в котором у меня есть длинная работающая процедура, которая принимает объект соединения и со временем открывает и закрывает его. Подпрограммам может быть безразлично, как был создан объект соединения. Поэтому преимущество состоит в том, чтобы разделить акт создания соединения с открытием и закрытием его.

Что касается вопроса управления ресурсами, я бы согласился, что это не проблема. Создание объекта соединения SQL само по себе не блокирует никаких ресурсов, это открывает его, который приобретает объединенное соединение. Если open возвращает исключение, я считаю разумным предположить, что соединение не было открыто.

Ответ 2

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

Одна из причин заключается в том, что существуют некоторые объекты, для которых требуется SqlConnection, но не обязательно их нужно открывать. Например, a SqlDataAdapter принимает SqlConnection и обрабатывает открытие и закрытие его внутри себя. Конечно, вы можете открыть соединение перед его передачей, но тогда вы должны быть явно закрыты.

Взяв несколько шагов назад, ответственность за то, что именно нужно сделать с SqlConnection, должно отвечать коду вызывающего кода.

Ответ 3

Вы можете поместить попытку/уловить вызов .Open() во второй "опасной" версии, чтобы при открытии исключений вы все же удаляли соединение.

Ответ 4

После достижения максимума в SqlConnection я почти уверен, что это действительно не имеет значения. Быстрое испытание, похоже, подтверждает это:

static void Main( string[] args )
{
   Func<SqlConnection> getConnection =
            () =>
               {
                  var connection =
                     new SqlConnection(
                        "Initial Catalog=myDatabase;Server=(local);Username=bogus;password=blah;Connect Timeout=10;" );

                  connection.Open();
                  return connection;
               };

   while(true)
   {
      try
      {
         using( var connection = getConnection() )
         {
            var cmd = new SqlCommand( "SELECT 1", connection ) {CommandType = CommandType.Text};
            cmd.ExecuteNonQuery();
         }
      }
      catch ( Exception )
      {
         // ignore exception
      }
   }
}

Я оставил этот код в течение нескольких минут с прикрепленным профилировщиком. Мало того, что он работает довольно быстро, он также не пропускает никакой памяти. Это в значительной степени убеждает меня, что нормально открывать соединение в методе GetConnection.

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