TransactionScope и область действия - находятся ли эти соединения в области действия? - PullRequest
2 голосов
/ 15 ноября 2011

Предположим, вы настроили объект TransactionScope, как показано на примере Microsoft здесь .Теперь предположим, что вам нужно обновить множество таблиц базы данных, и вы хотите, чтобы они все находились в области действия объекта TransactionScope.Непрерывное вложение объектов SqlConnection и SqlCommand глубиной 10 создаст беспорядок в исходном коде. Если вместо этого вы вызываете другие функции, которые создают соединения (например, на уровне доступа к данным), будут ли они находиться в области действия объекта TransactionScope?

Пример:

' Assume variable "x" is a business object declared and populated with data.
Using scope As New TransactionScope()

    Dal.Foo.SaveProducts(x.Products)
    Dal.Foo.SaveCustomer(x.Customer)
    Dal.Foo.SaveDetails(x.Details) 
    ' more DAL calls ...
    Dal.Foo.SaveSomethingElse(x.SomethingElse)

    scope.Complete()

End Using

Предположим, что каждая функция DAL содержит свои собственные операторы using для соединений.Пример:

Public Shared Sub SaveProducts(x As Object)

    Using conn As New SqlConnection("connection string")

        Using cmd As New SqlCommand("stored procedure name", conn)

            With cmd

                ' etc.                        

            End With

        End Using

    End Using

End Sub

Ответы [ 2 ]

4 голосов
/ 15 ноября 2011

Да, они будут внутри TransactionScope. В основном TransactionScope создает объект Transaction и устанавливает для него значение Transaction.Current.

Другими словами, это:

Using scope As New TransactionScope()
    ... blah blah blah ...
End Using

в основном то же самое, что и это:

try
{
    // Transaction.Current is a thread-static field
    Transaction.Current = new CommittableTransaction();
    ... blah blah blah ...
}
finally
{
    Transaction.Current.Commit(); // or Rollback(), depending on whether the scope was completed
    Transaction.Current = null;
}

Когда SqlConnection открыт, он проверяет, является ли Transaction.Current (в этом потоке) нулевым или нет, и если он не нулевой, то он зачисляется (если enlist = false в строке подключения). Таким образом, это означает, что SqlConnection.Open () не знает или не заботится, был ли открыт TransactionScope в этом методе или метод, который вызвал этот метод.

(Обратите внимание, что если вы хотите, чтобы SqlConnection в дочерних методах НЕ был в транзакции, вы можете создать внутренний TransactionScope с помощью TransactionScopeOption.Suppress)

2 голосов
/ 15 ноября 2011

Когда вы создаете TransactionScope, все соединения, которые вы открываете, пока TransactionScope существует, автоматически присоединяются к транзакции (они «автоматически зачисляются»). Так что вам не нужно передавать строки подключения.

Возможно, вы захотите, когда SQL Server видит разные транзакции (даже если они все содержатся в одной транзакции DTC), он не разделяет блокировки между ними. Если вы открываете слишком много соединений и много читаете и пишете, вы попадаете в тупик.

Почему бы не поместить активное соединение в какое-то глобальное место и использовать его?

Еще немного информации после некоторого исследования. Прочитайте это: TransactionScope автоматически переходит в MSDTC на некоторых машинах? .

Если вы используете SQL Server 2008 (и, вероятно, 2012, но не какую-либо другую базу данных), некоторые закулисные действия происходят за кулисами, и если вы открываете два соединения SQL , одно за другим , они собираются объединиться в одну транзакцию SQL, и у вас не будет проблем с блокировкой.

Однако , если вы используете другую базу данных или можете одновременно открыть два соединения, вы получите транзакцию DTC, что означает, что SQL Server не будет правильно управлять блокировками, и вы можете столкнуться с очень неприятные и неожиданные тупики.

Хотя легко убедиться, что вы работаете только на SQL Server 2008, убедиться, что вы не открыли два соединения одновременно, немного сложнее. Это очень легко забыть и сделать что-то вроде этого:

class MyPersistentObject
{
    public void Persist()
    {
         using(SQLConnection conn=...)
         {
             conn.Open()
             WriteOurStuff()
             foreach(var child in this.PersistedChildren)
                 child.Persist()
             WriteLogMessage()
         }
    }
}

Если дочерний метод Persist открывает другое соединение, ваша транзакция преобразуется в транзакцию DTC, и вы сталкиваетесь с потенциальными проблемами блокировки.

Поэтому я все еще предлагаю поддерживать соединение в одном месте и использовать его через свой DAL. Это не должна быть простая глобальная статическая переменная, вы можете создать простой класс ConnectionManager со свойством ConnectionManager.Current, которое будет содержать текущее соединение. Сделайте ConnectionManager.Current как [ThreadStatic], и вы решите большинство своих потенциальных проблем. Именно так работает TransactionScope за кулисами.

...