Помощник TransactionScope, который исчерпывает пул соединения бесперебойно - справка?

Некоторое время назад я задал вопрос о TransactionScope, возрастающем к MSDTC, когда я не ожидал это к. (Предыдущий вопрос)

То, к чему это свелось, было, в SQL2005, для использования TransactionScope, можно только инстанцировать и открыть единственный SqlConnection в жизни TransactionScope. С SQL2008 можно инстанцировать несколько SqlConnections, но только единственный может быть открыт в любой момент времени. SQL2000 будет всегда возрастать к DTC... мы не поддерживаем SQL2000 в нашем приложении, приложении WinForms, BTW.

Наше решение single-connection-only проблемы состояло в том, чтобы создать класс помощника TransactionScope, названный LocalTransactionScope (иначе 'LTS'). Это переносит TransactionScope и, самое главное, создает и поддерживает единственный экземпляр SqlConnection для нашего приложения. Хорошие новости, они работают - мы можем использовать LTS через разрозненные части кода, и они все присоединяются к окружающей транзакции.Очень мило. Проблема, каждый корневой созданный экземпляр LTS создаст и эффективно уничтожит соединение от пула соединения. 'Эффективно Уничтожают', я подразумеваю, что это инстанцирует SqlConnetion, который откроет новое соединение (по любой причине, это никогда не снова использует соединение от пула), и когда тот корневой LTS расположен, это закрывает и располагает SqlConnection, который, как предполагается, выпускает соединение назад с пулом так, чтобы это могло быть снова использовано, однако, это ясно никогда не снова используется. Чрезмерные увеличения размера пула, пока это не истрачено, и затем сбои приложения, когда max-pool-size+1 соединение устанавливается.

Ниже я присоединил разделенный вниз версия кода LTS и демонстрационного класса консольного приложения, который продемонстрирует исчерпание пула соединения. Для наблюдения чрезмерного увеличения размера пула соединения используйте SQL Server Монитор Действия 'Studio Managment' или этот запрос:

SELECT DB_NAME(dbid) as 'DB Name',
COUNT(dbid) as 'Connections'
FROM sys.sysprocesses WITH (nolock)
WHERE dbid > 0
GROUP BY dbid

Я присоединяю LTS здесь и демонстрационное консольное приложение, которое можно использовать, чтобы продемонстрировать для себя, что он использует соединения от пула и никогда повторное использование, ни выпустит их. Необходимо будет добавить ссылку на System.Transactions.dll для LTS для компиляции.

Информация на заметку: Это - корневой уровень LTS, который открывает и закрывает SqlConnection, который всегда открывает новое соединение в пуле. Вкладывание экземпляров LTS не имеет никакого значения, потому что только корневой экземпляр LTS создает SqlConnection. Как Вы видите, строка подключения всегда является тем же, таким образом, это должно снова использовать соединения.

Есть ли некоторое тайное условие, которому мы не удовлетворяем, который заставляет соединения не быть снова использованными? Там какое-либо решение к этому кроме выключения объединения полностью?

public sealed class LocalTransactionScope : IDisposable
{
      private static SqlConnection _Connection;    

      private TransactionScope _TransactionScope;
      private bool _IsNested;    

      public LocalTransactionScope(string connectionString)
      {
         // stripped out a few cases that need to throw an exception
         _TransactionScope = new TransactionScope();

         // we'll use this later in Dispose(...) to determine whether this LTS instance should close the connection.
         _IsNested = (_Connection != null);

         if (_Connection == null)
         {
            _Connection = new SqlConnection(connectionString);

            // This Has Code-Stink.  You want to open your connections as late as possible and hold them open for as little
            // time as possible.  However, in order to use TransactionScope with SQL2005 you can only have a single 
            // connection, and it can only be opened once within the scope of the entire TransactionScope.  If you have
            // more than one SqlConnection, or you open a SqlConnection, close it, and re-open it, it more than once, 
            // the TransactionScope will escalate to the MSDTC.  SQL2008 allows you to have multiple connections within a 
            // single TransactionScope, however you can only have a single one open at any given time. 
            // Lastly, let's not forget about SQL2000.  Using TransactionScope with SQL2000 will immediately and always escalate to DTC.
            // We've dropped support of SQL2000, so that's not a concern we have.
            _Connection.Open();
         }
      }

      /// 'Completes' the  this  encapsulates.
      public void Complete() { _TransactionScope.Complete(); }

      /// Creates a new  from the current  this  is managing.
      public SqlCommand CreateCommand() { return _Connection.CreateCommand(); }

      void IDisposable.Dispose() { this.Dispose(); }

      public void Dispose()
      {
          Dispose(true); GC.SuppressFinalize(this);
      }

      private void Dispose(bool disposing)
      {
         if (disposing)
         {
            _TransactionScope.Dispose();
            _TransactionScope = null;    

            if (!_IsNested)
            {
               // last one out closes the door, this would be the root LTS, the first one to be instanced.
               LocalTransactionScope._Connection.Close();
               LocalTransactionScope._Connection.Dispose();    

               LocalTransactionScope._Connection = null;
            }
         }
      }
   }

Это - Program.cs, который покажет исчерпание пула соединения:

class Program
{
      static void Main(string[] args)
      {
         // fill in your connection string, but don't monkey with any pooling settings, like
         // "Pooling=false;" or the "Max Pool Size" stuff.  Doesn't matter if you use 
         // Doesn't matter if you use Windows or SQL auth, just make sure you set a Data Soure and an Initial Catalog
         string connectionString = "your connection string here";

         List randomTables = new List();
         using (var nonLTSConnection = new SqlConnection(connectionString))
         using (var command = nonLTSConnection.CreateCommand())
         {
             command.CommandType = CommandType.Text;
             command.CommandText = @"SELECT [TABLE_NAME], NEWID() AS [ID]
                                    FROM [INFORMATION_SCHEMA].TABLES]
                                    WHERE [TABLE_SCHEMA] = 'dbo' and [TABLE_TYPE] = 'BASE TABLE'
                                    ORDER BY [ID]";

             nonLTSConnection.Open();
             using (var reader = command.ExecuteReader())
             {
                 while (reader.Read())
                 {
                     string table = (string)reader["TABLE_NAME"];
                     randomTables.Add(table);

                     if (randomTables.Count > 200) { break; } // got more than enough to test.
                 }
             }
             nonLTSConnection.Close();
         }    

         // we're going to assume your database had some tables.
         for (int j = 0; j < 200; j++)
         {
             // At j = 100 you'll see it pause, and you'll shortly get an InvalidOperationException with the text of:
             // "Timeout expired.  The timeout period elapsed prior to obtaining a connection from the pool.  
             // This may have occurred because all pooled connections were in use and max pool size was reached."

             string tableName = randomTables[j % randomTables.Count];

             Console.Write("Creating root-level LTS " + j.ToString() + " selecting from " + tableName);
             using (var scope = new LocalTransactionScope(connectionString))
             using (var command = scope.CreateCommand())
             {
                 command.CommandType = CommandType.Text;
                 command.CommandText = "SELECT TOP 20 * FROM [" + tableName + "]";
                 using (var reader = command.ExecuteReader())
                 {
                     while (reader.Read())
                     {
                         Console.Write(".");
                     }
                     Console.Write(Environment.NewLine);
                 }
             }

             Thread.Sleep(50);
             scope.Complete();
         }

         Console.ReadKey();
     }
 }

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

2 ответа

Ожидаемый шаблон TransactionScope / SqlConnection согласно MSDN :

using(TransactionScope scope = ...) {
  using (SqlConnection conn = ...) {
    conn.Open();
    SqlCommand.Execute(...);
    SqlCommand.Execute(...);
  }
  scope.Complete();
}

Таким образом, в примере MSDN соединение удалено внутри области, до область завершена. Ваш код, хотя и отличается, он удаляет соединение после области действия. Я не эксперт в вопросах TransactionScope и его взаимодействия с SqlConnection (я знаю некоторые вещи, но ваш вопрос довольно глубокий), и я не могу найти никаких спецификаций, что является правильным шаблоном. Но я бы посоветовал вам пересмотреть свой код и удалить одноэлементное соединение до того, как будет завершена самая внешняя область, аналогично образцу MSDN.

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

4
ответ дан 6 December 2019 в 00:06
поделиться

Законен ли этот код?

using(TransactionScope scope = ..)
{
    using (SqlConnection conn = ..)
    using (SqlCommand command = ..)
    {
        conn.Open();

        SqlCommand.Execute(..);
    }

    using (SqlConnection conn = ..) // the same connection string
    using (SqlCommand command = ..)
    {
        conn.Open();

        SqlCommand.Execute(..);
    }

    scope.Complete();
}
0
ответ дан 6 December 2019 в 00:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: