Транзакции. Локальные и распределенные транзакции

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

Кроме того, явно зачислять ресурсы в транзакцию не требуется. Любой диспетчер ресурсов System.Transactions (например SQL Server 2005) может обнаружить существование внешней транзакции, созданной областью, и автоматически зачислиться в эту транзакцию.

Создание области транзакции

Ниже представлен простой пример использования класса TransactionScope.

" This function takes arguments for 2 connection strings and commands to create a transaction " involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the " transaction is rolled back. To test this code, you can connect to two different databases " on the same server by altering the connection string, or to another 3rd party RDBMS " by altering the code in the connection2 code block. Public Function CreateTransactionScope(_ ByVal connectString1 As String, ByVal connectString2 As String, _ ByVal commandText1 As String, ByVal commandText2 As String) As Integer " Initialize the return value to zero and create a StringWriter to display results. Dim returnValue As Integer = 0 Dim writer As System.IO.StringWriter = New System.IO.StringWriter Try " Create the TransactionScope to execute the commands, guaranteeing " that both commands can commit or roll back as a single unit of work. Using scope As New TransactionScope() Using connection1 As New SqlConnection(connectString1) " Opening the connection automatically enlists it in the " TransactionScope as a lightweight transaction. connection1.Open() " Create the SqlCommand object and execute the first command. Dim command1 As SqlCommand = New SqlCommand(commandText1, connection1) returnValue = command1.ExecuteNonQuery() writer.WriteLine("Rows to be affected by command1: {0}", returnValue) " If you get here, this means that command1 succeeded. By nesting " the using block for connection2 inside that of connection1, you " conserve server and network resources as connection2 is opened " only when there is a chance that the transaction can commit. Using connection2 As New SqlConnection(connectString2) " The transaction is escalated to a full distributed " transaction when connection2 is opened. connection2.Open() " Execute the second command in the second database. returnValue = 0 Dim command2 As SqlCommand = New SqlCommand(commandText2, connection2) returnValue = command2.ExecuteNonQuery() writer.WriteLine("Rows to be affected by command2: {0}", returnValue) End Using End Using " The Complete method commits the transaction. If an exception has been thrown, " Complete is called and the transaction is rolled back. scope.Complete() End Using Catch ex As TransactionAbortedException writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message) Catch ex As ApplicationException writer.WriteLine("ApplicationException Message: {0}", ex.Message) End Try " Display messages. Console.WriteLine(writer.ToString()) Return returnValue End Function // This function takes arguments for 2 connection strings and commands to create a transaction // involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the // transaction is rolled back. To test this code, you can connect to two different databases // on the same server by altering the connection string, or to another 3rd party RDBMS by // altering the code in the connection2 code block. static public int CreateTransactionScope(string connectString1, string connectString2, string commandText1, string commandText2) { // Initialize the return value to zero and create a StringWriter to display results. int returnValue = 0; System.IO.StringWriter writer = new System.IO.StringWriter(); try { // Create the TransactionScope to execute the commands, guaranteeing // that both commands can commit or roll back as a single unit of work. using (TransactionScope scope = new TransactionScope()) { using (SqlConnection connection1 = new SqlConnection(connectString1)) { // Opening the connection automatically enlists it in the // TransactionScope as a lightweight transaction. connection1.Open(); // Create the SqlCommand object and execute the first command. SqlCommand command1 = new SqlCommand(commandText1, connection1); returnValue = command1.ExecuteNonQuery(); writer.WriteLine("Rows to be affected by command1: {0}", returnValue); // If you get here, this means that command1 succeeded. By nesting // the using block for connection2 inside that of connection1, you // conserve server and network resources as connection2 is opened // only when there is a chance that the transaction can commit. using (SqlConnection connection2 = new SqlConnection(connectString2)) { // The transaction is escalated to a full distributed // transaction when connection2 is opened. connection2.Open(); // Execute the second command in the second database. returnValue = 0; SqlCommand command2 = new SqlCommand(commandText2, connection2); returnValue = command2.ExecuteNonQuery(); writer.WriteLine("Rows to be affected by command2: {0}", returnValue); } } // The Complete method commits the transaction. If an exception has been thrown, // Complete is not called and the transaction is rolled back. scope.Complete(); } } catch (TransactionAbortedException ex) { writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message); } catch (ApplicationException ex) { writer.WriteLine("ApplicationException Message: {0}", ex.Message); } // Display messages. Console.WriteLine(writer.ToString()); return returnValue; }

Область транзакции начинает действовать после создания нового объекта TransactionScope. При создании областей рекомендуется использовать оператор using (как показано в примере кода). Оператор using предусмотрен как в C#, так и в Visual Basic; он работает как блок try...finally, позволяющий обеспечить правильное удаление области.

При создании экземпляра TransactionScope диспетчер транзакций определяет, в какой транзакции следует участвовать. После определения область всегда участвует в этой транзакции. Решение принимается на основе двух факторов: значения параметра TransactionScopeOption в конструкторе и наличия внешней транзакции. Внешняя транзакция - это транзакция, в рамках которой выполняется ваш код. Ссылку на внешнюю транзакцию можно получить, вызвав статическое свойство Current класса Transaction. Дополнительные сведения об использовании этого параметра см. в подразделе "Управление потоком транзакций с помощью объекта TransactionScopeOption" данного раздела.

Завершение области транзакции

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

Если данный метод не вызывается, транзакция прерывается, поскольку диспетчер транзакций интерпретирует это событие как системный сбой или возникновение исключения в области транзакции. Однако вызов этого метода не гарантирует, что транзакция будет зафиксирована. Это просто способ информирования диспетчера транзакций о состоянии. После вызова метода Complete доступ к внешней транзакции с помощью свойства Current невозможен. При попытке такого доступа возникает исключение.

Если транзакция создана объектом TransactionScope, фактическая фиксация транзакции выполняется диспетчером транзакций после последней строки кода в блоке using. Если транзакция создана не этим объектом, фиксация происходит при каждом вызове метода Commit владельцем объекта CommittableTransaction. При этом диспетчер транзакций вызывает диспетчеры ресурсов и сообщает им, чтобы они выполнили фиксацию или откат в зависимости от того, был ли вызван метод Complete объекта TransactionScope.

Использование оператора using обеспечивает вызов метода Dispose объекта TransactionScope даже в случае возникновения исключения. Вызов метода Dispose отмечает конец области транзакции. Исключения, возникающие после вызова данного метода, могут не воздействовать на транзакцию. Данный метод также восстанавливает предыдущее состояние внешней транзакции.

Если область создает транзакцию и эта транзакция прерывается, возникает исключение TransactionAbortedException. Если диспетчер транзакций не может принять решение о фиксации, возникает исключение TransactionIndoubtException. В случае фиксации транзакции исключения не возникают.

Откат транзакции

Если требуется откатить транзакцию, не следует вызывать метод Complete в пределах области транзакции. Например, можно вызвать исключение в пределах области. При этом будет выполнен откат транзакции, в которой участвует область.

Управление потоком транзакций с помощью объекта TransactionScopeOption

Область транзакции можно сделать вложенной, вызвав метод, использующий объект TransactionScope, из метода, использующего собственную область, как в случае метода RootMethod в следующем примере.

Void RootMethod() { using(TransactionScope scope = new TransactionScope()) { /* Perform transactional work here */ SomeMethod(); scope.Complete(); } } void SomeMethod() { using(TransactionScope scope = new TransactionScope()) { /* Perform transactional work here */ scope.Complete(); } }

Самая верхняя область транзакции называется корневой областью.

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

Предусмотрено три варианта поведения объекта TransactionScope:

    присоединиться к внешней транзакции или создать новую транзакцию, если она не существует;

    стать новой корневой областью, т. е. запустить новую транзакцию, представляющую собой новую внешнюю транзакцию внутри собственной области;

    не принимать участие в транзакции, в результате внешняя транзакция отсутствует.

Если область создана со значением Required и внешняя транзакция существует, область присоединяется к этой транзакции. Если внешняя транзакция отсутствует, область создает новую транзакцию и становится корневой областью. Это значение по умолчанию. В случае использования значения Required поведение кода, находящегося внутри области, должно быть одинаковым в обоих случаях: область является корневой или область только присоединяется к транзакции.

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

Если область создана со значением Suppress, она не принимает участие в транзакции независимо от существования внешней транзакции. В качестве внешней транзакции области, созданной с этим значением, всегда задано значение null.

Сводка рассмотренных выше значений представлена в следующей таблице.

После присоединения объекта TransactionScope к существующей внешней транзакции удаление объекта области может не привести к завершению транзакции, если только область не прервет транзакцию. Если внешняя транзакция была создана корневой областью, метод Commit вызывается для транзакции только после удаления корневой области. Если транзакция была создана вручную, она завершается в случае прерывания или в случае фиксации ее создателем.

В следующем примере показан объект TransactionScope, создающий три вложенных объекта области с разными значениями перечисления TransactionScopeOption.

Using(TransactionScope scope1 = new TransactionScope()) //Default is Required { using(TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Required)) { ... } using(TransactionScope scope3 = new TransactionScope(TransactionScopeOption.RequiresNew)) { ... } using(TransactionScope scope4 = new TransactionScope(TransactionScopeOption.Suppress)) { ... } }

В примере показан блок кода без внешней транзакции, создающий новую область (scope1) со значением Required. Область scope1 является корневой, поскольку она создает новую транзакцию (транзакцию A) и делает ее внешней транзакцией. Затем Scope1 создает три дополнительных объекта с разными значениями TransactionScopeOption. Например, объект scope2 создается со значением Required; поскольку существует внешняя транзакция, этот объект присоединяется к первой транзакции, созданной объектом scope1. Обратите внимание, что scope3 является корневой областью новой транзакции, а scope4 не имеет внешней транзакции.

Несмотря на то что значение по умолчанию Required является наиболее часто используемым значением перечисления TransactionScopeOption, каждое из остальных значений имеет свое уникальное назначение.

Значение Suppress полезно использовать, если требуется сохранить операции, выполняемые разделом кода, и не прерывать внешнюю транзакцию в случае сбоя этих операций (например, если требуется выполнить операции аудита или ведения журнала либо опубликовать события для подписчиков независимо от результата завершения внешней транзакции [фиксация или откат]). Это значение позволяет использовать раздел кода, не относящийся к транзакции, внутри области транзакции, как показано в следующем примере.

Using(TransactionScope scope1 = new TransactionScope()) { try { //Start of non-transactional section using(TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Suppress)) { //Do non-transactional work here } //Restores ambient transaction here } catch {} //Rest of scope1 }

Голосование во вложенной области

Несмотря на то что вложенная область может присоединяться к внешней транзакции корневой области, вызов метода Complete во вложенной области не оказывает воздействия на корневую область. Транзакция будет зафиксирована, только если все области из корневой области вплоть до последней вложенной области проголосуют за фиксацию транзакции. Если не вызвать метод Complete во вложенной области, то включающая транзакция будет немедленно прекращена, что отразится на корневой области.

Задание времени ожидания для объекта TransactionScope

Некоторые перегруженные конструкторы объекта TransactionScope принимают значение типа TimeSpan, которое используется для контроля времени ожидания транзакции. Нулевое значение указывает на бесконечное время ожидания. Значение бесконечного времени ожидания в основном полезно использовать при отладке, когда требуется изолировать проблему в бизнес-логике путем пошагового выполнения кода, но не требуется, чтобы истекло время ожидания отлаживаемой транзакции, пока осуществляется поиск проблемы. При использовании значения бесконечного времени ожидания во всех остальных случаях будьте чрезвычайно осторожны, поскольку оно переопределяет механизмы защиты от взаимоблокировок транзакций.

Как правило, при задании времени ожидания для объекта TransactionScope значения, отличные от значения по умолчанию, используются в двух случаях. Первый случай относится к процессу разработки, когда требуется проверить, как приложение обрабатывает прерванные транзакции. Задав малое значение времени ожидания (например, равное одной миллисекунде), можно вызвать прерывание транзакции и проверить код обработки ошибки. Второй случай относится к проверке участия области в состязании за ресурсы, приводящем к взаимоблокировкам; эта проверка заключается в задании времени ожидания, которое меньше значения по умолчанию. В этом случае требуется как можно скорее прервать транзакцию без ожидания истечения времени ожидания по умолчанию.

Если область присоединяется к внешней транзакции и задает время ожидания, меньшее заданного для внешней транзакции, к объекту TransactionScope применяется новое (меньшее) значение времени ожидания; при этом область должна завершиться в указанное время, иначе транзакция будет прервана автоматически. Если значение времени ожидания вложенной области превышает значение времени ожидания внешней транзакции, первое не оказывает никакого воздействия.

Задание уровня изоляции для объекта TransactionScope

Некоторые перегруженные конструкторы объекта TransactionScope принимают структуру типа TransactionOptions, позволяющую задать уровень изоляции в дополнение к значению времени ожидания. По умолчанию транзакция выполняется со значением уровня изоляции Serializable. Значения, отличные от Serializable, обычно используются в случае систем, в которых преобладают операции чтения. Это требует глубокого понимания теории обработки транзакций и семантики транзакций, проблем параллелизма, а также последствий, влияющих на целостность системы.

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

Каждый уровень изоляции, кроме Serializable, сопряжен с проблемой нарушения целостности, возникающей в результате доступа других транзакций к тем же самым данным. Разница между различными уровнями изоляции заключается в порядке использования блокировок для чтения и блокировок для записи. Блокировка может действовать только во время доступа транзакции к данным в диспетчере ресурсов или пока транзакция не будет зафиксирована или прервана. Первый вариант обеспечивает более высокую производительность, второй - более высокую согласованность. Два вида блокировок и два вида операций (чтение/запись) образуют четыре базовых уровня изоляции. Дополнительные сведения см. в разделе IsolationLevel.

При использовании вложенных объектов TransactionScope все вложенные области следует настроить на использование строго одного уровня изоляции, если им необходимо присоединиться к внешней транзакции. Если при попытке присоединения вложенного объекта TransactionScope к внешней транзакции обнаруживается, что для него задан другой уровень изоляции, возникает исключение ArgumentException.

Взаимодействие с транзакциями COM+

Чтобы задать способ взаимодействия с транзакциями COM+ при создании нового экземпляра TransactionScope, можно использовать перечисление EnterpriseServicesInteropOption в одном из конструкторов. Дополнительные сведения см. в разделе

Существует два основных вида сделок: транзакции подключения и внешние транзакции. Сделка соединения (например, SqlTransaction) привязана непосредственно к соединению db (например, SqlConnection), что означает, что вам нужно продолжать передавать соединение - в некоторых случаях ОК, но не разрешает «создавать/использовать/выпускать», использование и не разрешает работу с несколькими db. Пример (отформатирован для пробела):

Using (IDbTransaction tran = conn.BeginTransaction()) { try { //your code tran.Commit(); } catch { tran.Rollback(); throw; } }

Не слишком грязно, но ограничивается нашей связью «conn». Если мы хотим обратиться к различным методам, нам нужно передать «conn».

Альтернативой является внешняя транзакция; новый в.NET 2.0, объект TransactionScope (система.Transactions.dll) позволяет использовать по всему диапазону операций (подходящие провайдеры будут автоматически зачисляться в транзакцию с окружающими). Это позволяет легко встраиваться в существующий (не транзакционный) код и разговаривать с несколькими провайдерами (хотя DTC будет задействован, если вы поговорите с более чем одним).

Например:

Using(TransactionScope tran = new TransactionScope()) { CallAMethodThatDoesSomeWork(); CallAMethodThatDoesSomeMoreWork(); tran.Complete(); }

Обратите внимание, что эти два метода могут обрабатывать свои собственные соединения (open/use/close/dispose), но они беззвучно станут частью внешней транзакции без необходимости пропускать что-либо.

Если ваши ошибки кода, Dispose() будет вызываться без Complete (), поэтому он будет откат. Ожидаемое вложение и т. Д. Поддерживается, хотя вы не можете откатить внутреннюю транзакцию, но завершите внешнюю транзакцию: если кто-то недоволен, транзакция прерывается.

Другим преимуществом TransactionScope является то, что он не привязан к базам данных; любой поставщик транзакций может использовать его. WCF, например. Или есть даже некоторые модели объектов, совместимых с TransactionScope (например, классы.NET с возможностью отката - возможно, проще, чем память, хотя я никогда не использовал этот подход самостоятельно).

В общем, очень, очень полезный объект.

Некоторые оговорки:

  • В SQL Server 2000 TransactionScope немедленно переключится на DTC; это исправлено в SQL Server 2005 и выше, оно может использовать LTM (намного меньше служебных), пока вы не поговорите с 2 источниками и т. д., когда он повышен до DTC.
  • Существует сбой , что означает, что вам может потребоваться настроить строка подключения

Protected void Button1_Click(object sender, EventArgs e) { using (SqlConnection connection1 = new SqlConnection("Data Source=.\\SQLEXPRESS;AttachDbFilename=|DataDirectory|\\Database.mdf;Integrated Security=True;User Instance=True")) { connection1.Open(); //Start a local transaction. SqlTransaction sqlTran = connection1.BeginTransaction(); //Enlist a command in the current transaction. SqlCommand command = connection1.CreateCommand(); command.Transaction = sqlTran; try { //Execute two separate commands. command.CommandText = "insert into (drname,drspecialization,drday) values ("a","b","c")"; command.ExecuteNonQuery(); command.CommandText = "insert into (drname,drspecialization,drday) values ("x","y","z")"; command.ExecuteNonQuery(); //Commit the transaction. sqlTran.Commit(); Label3.Text = "Both records were written to database."; } catch (Exception ex) { //Handle the exception if the transaction fails to commit. Label4.Text = ex.Message; try { //Attempt to roll back the transaction. sqlTran.Rollback(); } catch (Exception exRollback) { //Throws an InvalidOperationException if the connection //is closed or the transaction has already been rolled //back on the server. Label5.Text = exRollback.Message; } } } }

Давным-давно вышел ADO.NET 2.0, а вместе с ним и сборка System.Transactions, содержащая класс TransactionScope - путеводитель в мир легкого и непринужденного использования транзакций. В сегодняшней статье я рассмотрю некоторые нюансы, возникающие при использовании этой дырявой, но такой симпатичной абстракции.

Итак, начиная с ADO.NET 2.0, для того чтобы заключить свой код в транзакцию, разработчику достаточно расположить его внутри блока TransactionScope:

Using (var transactionScope = new TransactionScope(TransactionScopeOption.Suppress, new TransactionOptions() { IsolationLevel = IsolationLevel.Serializable }) { //код внутри транзакции transactionScope.Complete(); }

Я использовал в конструкторе наиболее важные параметры - давайте их рассмотрим (в обратном порядке).

IsolationLevel

Вопрос использования или неиспользования RequiresNew и Suppress, безусловно, определяется требованиями алгоритма, но у меня на этот счет есть некоторые предубеждения. Дело в том, что TransactionScope в режимах RequiresNew и Suppress при наличии модифицирующих состояние базы данных операций делает невозможным использование старого трюка, когда код интеграционного теста заключается в транзакцию, которая по окончании теста откатывается, тем самым восстанавливая состояние базы данных:

Public void void IntegrationTest() { using (new TransactionScope()) { //код теста //не вызываем Complete } }

Если в тестируемом коде создаются TransactionScope в режиме Requires, то они подцепятся к тестовому TransactionScope, а значит мы сможем откатить все изменения. Если же в коде есть TransactionScope в режиме RequiresNew или Suppress, то откатить результат их работы из тестового TransactionScope мы не сможем. Стоит отметить, что наличие логики, завязанной на момент коммита транзакции (как в предыдущем примере), тоже делает невозможным использование этого приема.

Напоследок отмечу, что TransactionScope локален по отношению к потоку (потому что его реализация базируется на ThreadStatic-переменной). Если же вам необходимо использовать одну транзакцию из нескольких потоков, - воспользуйтесь классом

Локальные транзакции Local Transactions

В этой статье

В ADO.NET ADO.NET транзакции используются для связи нескольких задач, чтобы они выполнялись как одно целое. Transactions in ADO.NET ADO.NET are used when you want to bind multiple tasks together so that they execute as a single unit of work. Например, пусть приложение выполняет две задачи. For example, imagine that an application performs two tasks. Во-первых, оно заносит в таблицу сведения о заказе. First, it updates a table with order information. Во-вторых, обновляет таблицу, содержащую список товаров на складе, списывая заказанные элементы. Second, it updates a table that contains inventory information, debiting the items ordered. При сбое любой задачи, затем оба обновления откатываются. If either task fails, then both updates are rolled back.

Определение типа транзакции Determining the Transaction Type

Транзакция считается локальной транзакции при состоит из одной фазы и обрабатывается непосредственно базой данных. A transaction is considered to be a local transaction when it is a single-phase transaction and is handled by the database directly. Транзакция считается распределенной транзакции при координируется монитором транзакций и используется для разрешения транзакций резервные механизмы (например, двухфазную фиксацию). A transaction is considered to be a distributed transaction when it is coordinated by a transaction monitor and uses fail-safe mechanisms (such as two-phase commit) for transaction resolution.

Для выполнения локальных транзакций каждый поставщик данных платформы .NET Framework .NET Framework имеет свой собственный объект Transaction . Each of the .NET Framework .NET Framework data providers has its own Transaction object for performing local transactions. Если требуется выполнить транзакцию в базе данных SQL Server, выбирается транзакция System.Data.SqlClient . If you require a transaction to be performed in a SQL Server database, select a System.Data.SqlClient transaction. Для транзакции Oracle используйте поставщик System.Data.OracleClient . For an Oracle transaction, use the System.Data.OracleClient provider. Кроме того, DbTransaction класс, который доступен для написания независимого от поставщика кода, который требуется транзакций. In addition, there is a DbTransaction class that is available for writing provider-independent code that requires transactions.

Примечание

Транзакции наиболее эффективны, если они выполняются на сервере. Transactions are most efficient when they are performed on the server. При работе с базой данных SQL Server, интенсивно использующей явные транзакции, следует рассмотреть возможность их записи в виде хранимых процедур при помощи инструкции Transact-SQL BEGIN TRANSACTION. If you are working with a SQL Server database that makes extensive use of explicit transactions, consider writing them as stored procedures using the Transact-SQL BEGIN TRANSACTION statement.

Выполнение транзакций с использованием одного соединения Performing a Transaction Using a Single Connection

В ADO.NET ADO.NET транзакции управляются объектом Connection . In ADO.NET ADO.NET , you control transactions with the Connection object. Инициировать транзакцию можно с помощью метода BeginTransaction . You can initiate a local transaction with the BeginTransaction method. После начала транзакции при помощи свойства Transaction объекта Command к ней можно прикрепить команду. Once you have begun a transaction, you can enlist a command in that transaction with the Transaction property of a Command object. Затем в зависимости от успеха или ошибки компонентов транзакции можно зафиксировать или откатить изменения, сделанные в источнике данных. You can then commit or roll back modifications made at the data source based on the success or failure of the components of the transaction.

Примечание

Метод EnlistDistributedTransaction не должен использоваться для локальной транзакции. The EnlistDistributedTransaction method should not be used for a local transaction.

Область действия транзакции ограничена соединением. The scope of the transaction is limited to the connection. В следующем примере выполняется явная транзакция, состоящая из двух отдельных команд в блоке try . The following example performs an explicit transaction that consists of two separate commands in the try block. Команды выполняют инструкции INSERT для таблицы Production.ScrapReason в образце базы данных AdventureWorks в SQL Server, которые будут зафиксированы при отсутствии исключений. The commands execute INSERT statements against the Production.ScrapReason table in the AdventureWorks SQL Server sample database, which are committed if no exceptions are thrown. При возникновении исключения код в блоке catch произведет откат транзакции. The code in the catch block rolls back the transaction if an exception is thrown. При отмене транзакции или обрыве соединения до выполнения транзакции она откатывается автоматически. If the transaction is aborted or the connection is closed before the transaction has completed, it is automatically rolled back.

Пример Example

Чтобы осуществить транзакцию, выполните указанные ниже действия. Follow these steps to perform a transaction.

    Вызовите метод BeginTransaction объекта SqlConnection для отметки начала транзакции. Call the BeginTransaction method of the SqlConnection object to mark the start of the transaction. Метод BeginTransaction возвращает ссылку на транзакцию. The BeginTransaction method returns a reference to the transaction. Эта ссылка назначается объектам SqlCommand , прикрепленным к транзакции. This reference is assigned to the SqlCommand objects that are enlisted in the transaction.

    Присвойте объект Transaction свойству Transaction объекта SqlCommand . Assign the Transaction object to the Transaction property of the SqlCommand to be executed. Исключение вызывается, если команда выполняется при соединении с активной транзакцией, а объект Transaction не был назначен свойству Transaction объекта Command . If a command is executed on a connection with an active transaction, and the Transaction object has not been assigned to the Transaction property of the Command object, an exception is thrown.

    Выполните требуемые команды. Execute the required commands.

    Для выполнения транзакции вызовите метод Commit объекта SqlTransaction , для завершения транзакции вызовите метод Rollback . Call the Commit method of the SqlTransaction object to complete the transaction, or call the Rollback method to end the transaction. Транзакция откатывается, если соединение закрывается или пропадает до выполнения метода Commit либо Rollback . If the connection is closed or disposed before either the Commit or Rollback methods have been executed, the transaction is rolled back.

Следующий пример кода демонстрирует транзакционную логику, используемую ADO.NET ADO.NET с Microsoft SQL Server. The following code example demonstrates transactional logic using ADO.NET ADO.NET with Microsoft SQL Server.

Using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); // Start a local transaction. SqlTransaction sqlTran = connection.BeginTransaction(); // Enlist a command in the current transaction. SqlCommand command = connection.CreateCommand(); command.Transaction = sqlTran; try { // Execute two separate commands. command.CommandText = "INSERT INTO Production.ScrapReason(Name) VALUES("Wrong size")"; command.ExecuteNonQuery(); command.CommandText = "INSERT INTO Production.ScrapReason(Name) VALUES("Wrong color")"; command.ExecuteNonQuery(); // Commit the transaction. sqlTran.Commit(); Console.WriteLine("Both records were written to database."); } catch (Exception ex) { // Handle the exception if the transaction fails to commit. Console.WriteLine(ex.Message); try { // Attempt to roll back the transaction. sqlTran.Rollback(); } catch (Exception exRollback) { // Throws an InvalidOperationException if the connection // is closed or the transaction has already been rolled // back on the server. Console.WriteLine(exRollback.Message); } } } Using connection As New SqlConnection(connectionString) connection.Open() " Start a local transaction. Dim sqlTran As SqlTransaction = connection.BeginTransaction() " Enlist a command in the current transaction. Dim command As SqlCommand = connection.CreateCommand() command.Transaction = sqlTran Try " Execute two separate commands. command.CommandText = _ "INSERT INTO Production.ScrapReason(Name) VALUES("Wrong size")" command.ExecuteNonQuery() command.CommandText = _ "INSERT INTO Production.ScrapReason(Name) VALUES("Wrong color")" command.ExecuteNonQuery() " Commit the transaction sqlTran.Commit() Console.WriteLine("Both records were written to database.") Catch ex As Exception " Handle the exception if the transaction fails to commit. Console.WriteLine(ex.Message) Try " Attempt to roll back the transaction. sqlTran.Rollback() Catch exRollback As Exception " Throws an InvalidOperationException if the connection " is closed or the transaction has already been rolled " back on the server. Console.WriteLine(exRollback.Message) End Try End Try End Using

См. также See also

  • Транзакции и параллельность Transactions and Concurrency
  • Распределенные транзакции Distributed Transactions
  • Интеграция System.Transactions с SQL Server System.Transactions Integration with SQL Server
  • Центр разработчиков наборов данных и управляемых поставщиков ADO.NET ADO.NET Managed Providers and DataSet Developer Center

Введение

Транзакция - это последовательность операций, выполняемых как единое целое. Благодаря объединению взаимосвязанных операций в транзакцию гарантируется согласованность и целостность данных в системе, несмотря на любые ошибки, которые могли возникнуть в ходе транзакции. Для успешного выполнения транзакции необходимо успешное завершение всех ее операций.

У транзакции есть начало и конец, определяющие ее границы (transaction boundaries), внутри которых транзакция может охватывать различные процессы и компьютеры. Все ресурсы, используемые в ходе данной транзакции, считаются участвующими в этой транзакции. Для поддержания целостности используемых ресурсов транзакция должна обладать свойствами ACID: Atomicity (атомарность), Consistency (целостность), Isolation (изоляция) и Durability (отказоустойчивость). Подробнее об основах обработки транзакций см. Processing Transactions (EN) в Microsoft .NET Framework SDK и Transaction Processing в Microsoft Platform SDK.

В этой статье мы покажем, как выполнять локальные и распределенные транзакции в приложениях Microsoft .NET.

Локальные и распределенные транзакции

Локальной называется транзакция, областью действия которой является один ресурс, поддерживающий транзакции, - база данных Microsoft ® SQL Server™, очередь сообщений MSMQ и др. Например, отдельно взятая СУБД может вводить в действие правила ACID, когда в ее распоряжении имеются все данные, участвующие в транзакции. В SQL Server предусмотрен внутренний диспетчер транзакций (transaction manager), предоставляющий функциональность для фиксации (commit) и отката (rollback) транзакций.

Распределенные транзакции могут использовать гетерогенные ресурсы, поддерживающие транзакции, включать самые разнообразные операции, например выборку информации из базы данных SQL Server, считывание сообщений Message Queue Server и запись в другие базы данных. Программирование распределенных приложений упрощается программным обеспечением, способным координировать фиксацию и откат, а также восстановление данных, хранящихся в различных ресурсах. Одной из таких технологий является DTC (Microsoft Distributed Transaction Coordinator). DTC реализует протокол двухфазной фиксации (two-phase commit protocol), гарантирующий непротиворечивость результатов транзакции во всех ресурсах, участвующих в этой транзакции. DTC поддерживает только приложения, в которых реализуются совместимые с ним интерфейсы управления транзакциями. Эти приложения называются диспетчерами ресурсов (Resource Managers) (дополнительную информацию по этой теме см. в Distributed Transactions (EN) в.NET Framework Developer’s Guide). В настоящее время существует довольно много таких приложений - MSMQ, Microsoft SQL Server, Oracle, Sybase и др.

Транзакции баз данных

Вызов хранимой процедуры (stored procedure), которая заключает необходимые операции в операторы BEGIN TRANSACTION и COMMIT/ROLLBACK TRANSACTION, дает наилучшую производительность, позволяя выполнить транзакцию с разовым обменом данными с сервером (single round-trip) . Кроме того, транзакции баз данных могут быть вложенными, т. е. внутри активной транзакции можно начать выполнение новой транзакции.

В следующем фрагменте кода оператор BEGIN TRANSACTION начинает новую транзакцию. Транзакцию можно завершить двумя способами: либо фиксацией изменений в базе данных оператором COMMIT TRANSACTION, либо (при возникновении какой-либо ошибки) отменой всех изменений оператором ROLLBACK TRANSACTION.

CREATE PROCEDURE Proc1 … AS -- Начинаем транзакцию BEGIN TRANSACTION -- Выполняем операции транзакции … -- Проверяем наличие ошибок If @@Error <> 0 -- Откатываем транзакцию ROLLBACK TRANSACTION … -- Фиксируем транзакцию COMMIT TRANSACTION

Показанная ниже хранимая процедура принимает в качестве входного параметра XML-представление информации о заказе (order). Для выполнения соответствующих вставок в таблицы Orders и OrderDetails хранимая процедура загружает и анализирует XML с помощью системной хранимой процедуры sp_xmlpreparedocument . Как видно из исходного кода, все операции хранимой процедуры включены в явно выполняемую транзакцию, поэтому при неудачном завершении любой операции все внесенные изменения отменяются (откатываются).

Заметьте, что процедура устанавливает флаг XACT_ABORT в ON, указывая, что SQL Server должен автоматически откатить транзакцию, если выполнить какой-нибудь оператор не удастся.

CREATE PROCEDURE InsertOrder @Order NVARCHAR(4000) = NULL , @OrderId int Output AS SET NOCOUNT ON DECLARE @hDoc INT DECLARE @PKId INT -- Указываем, что SQL Server должен автоматически откатывать текущую -- транзакцию, если оператор Transact-SQL генерирует ошибку периода -- выполнения (run-time error). SET XACT_ABORT ON -- Начинаем транзакцию BEGIN TRANSACTION -- Загружаем и анализируем содержимое входного XML-представления -- информации о заказе, а затем помещаем его в XMLDocument EXEC sp_xml_preparedocument @hDoc OUTPUT, @Order -- Выбираем заголовок заказа из XMLDocument-узла Orders -- и вставляем его в таблицу Orders INSERT Orders(CustomerId, OrderDate, ShipToName, ShipToAddressId, OrderStatus) SELECT CustomerId, CONVERT(DateTime,OrderDate), ShipToName, ShipToAddressId, OrderStatus FROM OPENXML(@hDoc, "/NewDataSet/Orders") WITH (CustomerId int "CustomerId", OrderDate nvarchar(23) "OrderDate", ShipToName nvarchar(40) "ShipToName", ShipToAddressId int "ShipToAddressId", OrderStatus int "OrderStatus") -- Выбираем OrderId заказа, только что вставленного в таблицу Orders -- для использования при вставке позиций заказа (order details) SELECT @PKId = @@IDENTITY -- Выбираем позиции заказа из XMLDocument-узла Details -- и вставляем их в таблицу OrderDetails INSERT OrderDetails (OrderId, ItemId, UnitPrice, Quantity) SELECT @PKId as OrderId, ItemId, UnitPrice, Quantity FROM OPENXML(@hDoc, "/NewDataSet/Details") WITH (ItemId int "ItemId", UnitPrice money "UnitPrice", Quantity int "Quantity") -- Присваиваем значение выходному параметру Select @OrderId = @PKId -- Фиксируем транзакцию COMMIT TRANSACTION EXEC sp_xml_removedocument @hDoc RETURN 0 GO

Хотя такой подход обеспечивает хорошую производительность, при его использовании приходится программировать на Transact SQL, а это сложнее, чем на языке, совместимом с.NET.

Транзакции вручную

Транзакции, выполняемые вручную (manual transactions), позволяют явно управлять границами транзакции с помощью команд начала и конца транзакции. В этой модели также поддерживаются вложенные транзакции, т. е. вы можете начинать новую транзакцию в рамках активной транзакции. Расплата за возможность такого управления заключается в том, что на вас ложится бремя включения в транзакцию нужных ресурсов данных и их координация. Встроенной поддержки распределенных транзакций нет, поэтому управление распределенной транзакцией вручную потребует взять на себя массу обязанностей. Вам придется управлять каждым подключением к ресурсу и его использованием, а также реализовать поддержку свойств ACID в транзакции.

Транзакции ADO.NET, выполняемые вручную

Транзакции вручную поддерживают оба провайдера данных Microsoft ADO.NET, которые предоставляют набор объектов, позволяющих создавать соединение с хранилищем данных, начинать транзакцию, фиксировать или откатывать ее и, наконец, закрывать соединение. В своих примерах мы будем использовать управляемый ADO. NET-провайдер SQL (ADO.NET SQL managed provider).

Для выполнения операций в рамках единой транзакции нужно создать объект SQLTransaction , начать транзакцию с помощью объекта SQLConnection , добиться, чтобы все операции над базой данных проходили в этой транзакции, и зафиксировать или отменить транзакцию. Объект SQLTransaction предоставляет целый ряд свойств и методов для управления транзакцией. При успешном выполнении всех операций транзакции вы можете зафиксировать изменения в базе данных методом Commit . Для отката изменений применяется метод Rollback объекта SQLTransaction .

Для выполнения SQL-команды в транзакции свойство Transaction объекта Command необходимо установить на уже начатую транзакцию.

Visual Basic .NET

Dim conn as SQLConnection Dim cmd as SQLCommand Dim txn As SQLTransaction conn = New SQLConnection("ConnString") cmd = New SQLCommand " Открываем соединение conn.Open() " Начинаем транзакцию txn = conn.BeginTransaction() cmd.Transaction = Txn

Visual C# .NET

SQLConnection Conn = New SQLConnection("ConnString"); SQLCommand Cmd = New SQLCommand; // Открываем соединение Conn.Open(); // Начинаем транзакцию SQLTransaction Txn = Conn.BeginTransaction(); // Настраиваем свойство Transaction на транзакцию, где выполняется // SQL-команда Cmd.Transaction = Txn;

В примере, представленном ниже, мы выполняем в рамках транзакции две SQL-команды. Первая вставляет заголовок заказа (order header) в таблицу Orders и возвращает OrderId только что созданного заказа. Этот OrderId используется во второй команде, которая вставляет позиции этого заказа в таблицу OrderDetails. Транзакция отменяется, если хотя бы одна из двух команд терпит неудачу; при этом строки в базу данных не добавляются.

Visual Basic .NET

Dim conn As SqlConnection Dim cmd As SqlCommand Dim tran As SqlTransaction " Создаем новое соединение conn = New SqlConnection("ConnString") " Открываем соединение conn.Open() " Создаем объект Command cmd = New SqlCommand() " Создаем транзакцию tran = conn.BeginTransaction " Настраиваем свойство Transaction на транзакцию, где выполняется " SQL-команда cmd.Transaction = tran Try " Вставляем заголовок заказа. " Настраиваем свойства Command With cmd .CommandType = CommandType.StoredProcedure .CommandText = "InsertOrderHeader" .Connection = conn " Добавляем входные и выходные параметры.Parameters.Add("@CustomerId", SqlDbType.Int) .Parameters("@CustomerId").Direction = ParameterDirection.Input … " Устанавливаем значения параметров.Parameters("@CustomerId").Value = 1 … " Выполняем команду .ExecuteNonQuery() " Получаем OrderId добавленного заголовка заказа OrderId = .Parameters("@OrderId").Value " Очищаем параметры для следующей команды.Parameters.clear() End With " Вставляем позиции заказа " Настраиваем свойства Command With cmd .CommandType = CommandType.StoredProcedure .CommandText = "InsertOrderDetail" .Connection = conn " Добавляем параметры.Parameters.Add("@OrderId", SqlDbType.Int) .Parameters("@OrderId").SourceColumn = "OrderId" .Parameters("@OrderId").Direction = ParameterDirection.Input … " Устанавливаем значения параметров.Parameters("@OrderId").Value = OrderId .Parameters("@ItemId").Value = 100 … " Выполняем команду .ExecuteNonQuery() " Повторяем показанные выше строки для каждой позиции заказа End With " Фиксируем транзакцию tran.Commit() Catch " Откатываем транзакцию tran.Rollback() Finally " Код очистки. " Закрываем соединение. conn.Close() End Try

Как видите, две команды выполняются как часть одной транзакции. Если одна из них терпит неудачу, транзакция отменяется, и любые изменения в базе данных откатываются. Заключив код в блок try/catch/finally, вы гарантируете корректное выполнение транзакции: она фиксируется в самом конце блока try после успешного выполнения обеих SQL-команд. Любое исключение перехватывается в блоке catch, где транзакция отменяется и изменения, внесенные в ходе этой транзакции, откатываются.

Управление транзакциями через объекты ADO.NET приводит к менее эффективному блокированию, чем при использовании явных транзакций в хранимых процедурах. Причина в том, что при транзакциях ADO.NET, выполняемых вручную, требуется как минимум столько же двусторонних обменов данными с СУБД, сколько операций выполняется в транзакции плюс два обмена в ее начале и конце. Блокировки удерживаются в течение всего времени передачи вызовов из кода ADO.NET на сервер базы данных и обратно.

Транзакции MSMQ, выполняемые вручную

NET Framework предусматривает два вида поддержки транзакций MSMQ: внутреннюю (для транзакций вручную) и внешнюю (для автоматических транзакций). В первом случае в рамках транзакции возможен прием или передача нескольких сообщений. Во втором - сообщения участвуют в транзакциях DTC (Distributed Transaction Coordinator).

Транзакции MSMQ, выполняемые вручную, поддерживаются классом MessageQueueTransaction и обрабатываются исключительно ядром MSMQ. Подробности см. в статье Дункана Мак-Кензи (Duncan Mackenzie) (EN) .

Автоматические транзакции

Поддержка автоматических транзакций в.NET Framework опирается на службы MTS/COM+. COM+ использует DTC в качестве диспетчера и координатора транзакций при выполнении транзакций в распределенной среде. Это позволяет приложениям.NET выполнять транзакции, охватывающие разнообразные операции над множеством ресурсов, например вставку заказа в базу данных SQL Server, запись сообщения в очередь MSMQ (Microsoft Message Queue), отправку сообщения электронной почты и считывание информации из базы данных Oracle.

Предоставляя модель программирования на основе декларативных транзакций (declarative transactions), COM+ резко упрощает выполнение транзакций, в которых участвуют гетегрогенные ресурсы. Но учтите, что за это приходится расплачиваться снижением производительности, связанным с издержками взаимодействия DTC и COM; кроме того, поддержка вложенных транзакций отсутствует.

Страницы ASP.NET, методы Web-сервисов и. NET-классы можно помечать как транзакционные, присваивая им атрибут декларативной транзакции (declarative transaction attribute).

ASP.NET

<@ Page Transaction="Required" > Web-сервис ASP.NET <%@ WebService Language="VB" Class="Class1" %> <%@ assembly name="System.EnterpriseServices" %> … Public Class Class1 Inherits WebService <WebMethod(TransactionOption:= TransactionOption.RequiresNew) > _ Public Function Method1() …

Для участия в автоматических транзакциях. NET-класс должен наследовать от System.EnterpriseServices.ServicedComponent , который обеспечивает выполнение класса в COM+. Если вы сделаете именно так, COM+, взаимодействуя с DTC, создаст распределенную транзакцию и подключит к ней все необходимые ресурсы без вашего участия. Кроме того, вам нужно присвоить классу атрибут декларативной транзакции, чтобы определить его поведение при выполнении транзакции.

Visual Basic .NET

Visual C# .NET

public class Class1: ServicedComponent { … }

Транзакционный атрибут класса принимает одно из следующих значений:

  • Disabled. Указывает, что объект никогда не создается в транзакции COM+. Для поддержки транзакций объект может обращаться к DTC напрямую.
  • NotSupported. Указывает, что объект никогда не создается в транзакции.
  • Supported. Указывает, что объект выполняется в контексте транзакции своего создателя. Если объект сам является корневым или если его создатель не выполняется в транзакции, объект создается вне транзакции.
  • Required. Указывает, что объект выполняется в контексте транзакции своего создателя. Если объект сам является корневым или если его создатель не выполняется в транзакции, при создании такого объекта создается новая транзакция.
  • RequiresNew. Указывает, что объекту нужна транзакция и что при его создании создается новая транзакция.

В следующем коде содержится. NET-класс, настроенный на выполнение в COM+. Кроме того, атрибутам сборки присваиваются значения, необходимые для конфигурирования свойств COM+-приложения.

Visual Basic .NET

Imports System Imports System.Runtime.CompilerServices Imports System.EnterpriseServices Imports System.Reflection " Детали регистрации. " Имя COM+-приложения в том виде, в каком оно присутствует " в каталоге COM+ " Строгое имя (strong name) для сборки (assembly) Public Class Class1 Inherits ServicedComponent Public Sub Example1() … End Sub End Class

Visual C# .NET

using System; using System.Runtime.CompilerServices; using System.EnterpriseServices; using System.Reflection; // Детали регистрации. // Имя COM+-приложения в том виде, в каком оно присутствует // в каталоге COM+ // Строгое имя для сборки public class Class1: ServicedComponent { public void Example1() { … } }

<Assembly: ApplicationName(“Class1”) > указывает имя COM+-приложения, в которое устанавливаются компоненты сборки. <Assembly: ApplicationActivation(ActivationOption.Server) > определяет, является ли это приложение сервером или библиотекой. Когда вы указываете ApplicationActivation(ActivationOption.Server) , сборку необходимо установить в GAC (global assembly cache) с помощью утилиты командной строки gacutil (GacUtil.exe).

Для преобразования сборки в библиотеку типов, регистрации библиотеки типов и ее установки в заданное COM+-приложение можно использовать утилиту командной строки Regsvcs.exe. Кроме того, эта утилита настраивает свойства, добавленные в сборку программным способом. Например, если в сборке указано ApplicationActivation(ActivationOption.Server) , утилита создаст серверное приложение. Если вызванная сборка еще не установлена в COM+, исполняющая среда создаст и зарегистрирует библиотеку типов, а затем установит ее в COM+. COM+-приложение, созданное для сборки, можно просмотреть и настроить в оснастке Component Services.

Процесс создания, регистрации и использования обслуживаемых компонентов (serviced components) подробно рассматривается в разделе Writing Serviced Components (EN) руководства.NET Framework Developer’s Guide.

В следующем коде показывается транзакционный класс, сконфигурированный для запуска под управлением COM+, в котором в рамках транзакции выполняются две SQL-команды. Первая вставляет заголовок заказа в таблицу заказов и возвращает OrderId добавленного заказа. Этот OrderId используется второй командой при вставке позиций заказа в таблицу OrderDetails. Транзакция отменяется, если не удалось выполнить хотя бы одну из двух команд; при этом записи в базу данных не добавляются.

Visual Basic .NET

<Transaction(TransactionOption.Required) > Public Class Class1 Inherits ServicedComponent Public Sub Example1() … Try " Создаем новое соединение conn = New SqlConnection("ConnString") " Открываем соединение conn.Open() " Создаем новый объект Command cmd = New SqlCommand() " Вставляем заголовок заказа " Присваиваем значения свойствам Command With cmd1 .CommandType = CommandType.StoredProcedure .CommandText = "InsertOrderHeader" .Connection = conn " Добавляем входные и выходные параметры.Parameters.Add("@CustomerId", SqlDbType.Int) … .ExecuteNonQuery() " Очищаем параметры для следующей команды .Parameters.clear() End With " Вставляем позиции заказа " Настраиваем свойства Command With cmd .CommandType = CommandType.StoredProcedure .CommandText = "InsertOrderDetail" .Connection = conn " Добавляем параметры.Parameters.Add("@OrderId", SqlDbType.Int) … " Выполняем команду .ExecuteNonQuery() " Повторяем эти строки для каждой позиции заказа End With " Фиксируем транзакцию ContextUtil.SetComplete() Catch " Откатываем транзакцию ContextUtil.SetAbort() Finally " Код очистки End Try End Sub

Используя класс System.EnterpriseServices.ContextUtil , можно получить информацию о контексте COM+-объекта. Этот класс предоставляет методы SetComplete и SetAbort , позволяющие явным образом фиксировать и откатывать транзакцию. Легко догадаться, что метод ContextUtil.SetComplete вызывается в самом конце блока try, когда все операции выполнены успешно и нужно зафиксировать транзакцию. Все исключения перехватывается в блоке catch, где транзакция отменяется с помощью ContextUtil.SetAbort .

Кроме того, с помощью класса-атрибута (attribute class) System.EnterpriseServices.AutoComplete можно добиться, чтобы обслуживаемый компонент автоматически определял, фиксировать или откатывать транзакцию. Компонент «голосует» за фиксацию транзакции, если вызов метода завершился успешно. Если вызов метода привел к генерации исключения, транзакция автоматически отменяется; явный вызов ContextUtil.SetAbort не нужен. Чтобы воспользоваться этой возможностью, вставьте атрибут перед методом класса:

Visual Basic .NET

Public Class Class1 Inherits ServicedComponent Public Sub Example1() … End Sub End Class

Visual C# .NET

public class Class1: ServicedComponent { public void Example1() { … } }

Атрибут <AutoComplete > предлагает самый простой способ программирования транзакций, позволяя обходиться без явных фиксации и отката транзакций. Вы получите точно такую же функциональность, что и в предыдущем примере, где для отмены транзакции явно вызывался метод ContextUtil.SetAbort в блоке catch. Недостаток этого способа в том, что выполнение транзакции неочевидно и при сопровождении кода о ней можно забыть. Кроме того, нет возможности вывода дружественного к пользователю сообщения, если транзакция терпит неудачу. В таких случаях следует явно перехватывать все исключения, вызывать ContextUtil.SetAbort и показывать требуемое сообщение.

В системах, где нужно выполнять транзакции, использующие MSMQ и другие ресурсы, единственно возможный выбор - применение транзакций DTC или COM+. DTC координирует все диспетчеры ресурсов, участвующие в распределенной транзакции, а также управляет деятельностью, связанной с транзакциями. Пример распределенной транзакции MSMQ и SQL Server см. в статье Дункана Мак-Кензи Reliable Messaging with MSMQ and .NET (EN) .

Заключение

При использовании каждой из технологий работы с транзакциями приходится идти на компромис между производительностью приложения и удобством сопровождения кода. Запуск реализованной в хранимой процедуре транзакции базы данных обеспечивает наилучшую производительность, так как требуется лишь один двусторонний обмен информацией с базой данных. Кроме того, обеспечивается гибкость управления транзакциями, поскольку явно указываются начало и завершение транзакции. Но, хотя этот способ дает хорошую производительность и гибкость, приходится программировать на Transact SQL, что не так легко, как на языках.NET.

Транзакции, выполняемые вручную с помощью транзакционных объектов ADO.NET, просты в программировании. Благодаря явному применению инструкций начала и завершения транзакции, возможно гибкое управление границами транзакции. Платой за эту легкость и гибкость является снижение производительности за счет дополнительных двусторонних обменов информацией с базой данных при выполнении транзакции.

Автоматическая транзакция - единственно возможный выбор, когда транзакция использует несколько диспетчеров ресурсов, поддерживающих транзакции, например базы данных SQL Server, очереди сообщений MSMQ и т. д. Они значительно упрощают разработку приложений и предъявляют более низкие требования к квалификации программиста. Однако из-за того, что всю работу по координации выполняет служба COM+, возможны дополнительные издержки.