Доступ к текущей транзакции
Если транзакция активна в точке ввода кода CLR, выполняющегося на SQL Server , то транзакция является доступной через класс System.Transactions.Transaction. Для доступа к текущей транзакции используется свойство Transaction.Current. В большинстве случаев нет необходимости получить явный доступ к транзакции. Для соединений с базами данных ADO.NET автоматически проверяет Transaction.Current, когда вызывается метод Connection.Open, и явно прикрепляет соединение к этой транзакции (если только в строке соединения ключевое слово Enlist не равно false).
Может возникнуть необходимость использовать объект Transaction напрямую в следующих случаях:
если необходимо прикрепить ресурс, который не осуществляет автоматическое прикрепление, или который по какой-либо причине не был прикреплен во время инициализации;
если необходимо явно прикрепить ресурс к транзакции;
если необходимо завершить внешнюю транзакцию внутри хранимой процедуры или функции. В этом случае используется TransactionScope. Например, следующий код откатит текущую транзакцию:
using(TransactionScope transactionScope = new TransactionScope(TransactionScopeOptions.Required)) { }
В остальной части раздела описываются другие способы отмены внешней транзакции.
Отмена внешней транзакции
Внешнюю транзакцию можно отменить из управляемой процедуры или функции следующими способами.
Управляемая процедура или функция может возвращать значения с помощью выходного параметра. Вызов процедуры Transact-SQL может проверить возвращаемое значение, и, в случае необходимости, выполнить ROLLBACK TRANSACTION.
Управляемая процедура или функция может выбрасывать пользовательское исключение. Вызов процедуры Transact-SQL может перехватить исключение, выбрасываемое управляемой процедурой или функцией в блоке try-catch и выполнить ROLLBACK TRANSACTION.
Управляемая процедура или функция может отменить текущую транзакцию путем вызова метода Transaction.Rollback, если встречается определенное условие.
При вызове внутри управляемой процедуры или функции метод Transaction.Rollback выбрасывает исключение с неоднозначным сообщение об ошибке, и может быть перекрыт в блоке try-catch. Сообщение об ошибке похоже на следующее:
Msg 3994, Level 16, State 1, Procedure uspRollbackFromProc, Line 0Transaction is not allowed to roll back inside a user defined routine, trigger or aggregate because the transaction is not started in that CLR level. Change application logic to enforce strict transaction nesting.
Это ожидаемое исключение, поэтому для продолжения выполнения кода необходим блок try-catch. Без блока try-catch исключение будет немедленно вызвано в вызывающей процедуре Transact-SQL и выполнение управляемого кода завершится. Если выполнение управляемого кода завершается, то вызывается другое исключение:
Msg 3991, Level 16, State 1, Procedure uspRollbackFromProc, Line 1 The context transaction which was active before entering user defined routine, trigger or aggregate " uspRollbackFromProc " has been ended inside of it, which is not allowed. Change application logic to enforce strict transaction nesting. The statement has been terminated.
Это исключение также является ожидаемым, и для продолжения выполнения необходим блок try-catch вокруг инструкции Transact-SQL, которая выполняет действие, выполняющее триггер. Несмотря на два выданных исключения, транзакция откатывается, и изменения не фиксируются.
Примеры
Ниже приведен пример транзакции, которая откатывается из управляемой процедуры с помощью метода Transaction.Rollback. Обратите внимание на блок try-catch вокруг метода Transaction.Rollback в управляемом коде. Transact-SQL-сценарий создает сборку и управляемую хранимую процедуру. Следует иметь ввиду, что инструкция EXEC uspRollbackFromProc перекрывается в блоке try-catch block, так что исключение, выдаваемое при завершении выполнения управляемой процедуры, перехватывается.
using System;using System.Data;using System.Data.SqlClient;using System.Data.SqlTypes;using Microsoft.SqlServer.Server;using System.Transactions;public partial class StoredProcedures{[Microsoft.SqlServer.Server.SqlProcedure]public static void uspRollbackFromProc(){ using (SqlConnection connection = new SqlConnection(@"context connection=true")) { // Open the connection. connection.Open(); bool successCondition = true; // Success condition is met. if (successCondition) { SqlContext.Pipe.Send("Success condition met in procedure."); // Perform other actions here. } // Success condition is not met, the transaction will be rolled back. else { SqlContext.Pipe.Send("Success condition not met in managed procedure. Transaction rolling back..."); try { // Get the current transaction and roll it back. Transaction trans = Transaction.Current; trans.Rollback(); } catch (SqlException ex) { // Catch the expected exception. // This allows the connection to close correctly. } } // Close the connection. connection.Close(); }}};
Imports SystemImports System.DataImports System.Data.SqlClientImports System.Data.SqlTypesImports Microsoft.SqlServer.ServerImports System.TransactionsPartial Public Class StoredProcedures<Microsoft.SqlServer.Server.SqlProcedure()> _Public Shared Sub uspRollbackFromProc () Using connection As New SqlConnection("context connection=true") ' Open the connection. connection.Open() Dim successCondition As Boolean successCondition = False ' Success condition is met. If successCondition Then SqlContext.Pipe.Send("Success condition met in procedure.") ' Success condition is not met, the transaction will be rolled back. Else SqlContext.Pipe.Send("Success condition not met in managed procedure. Transaction rolling back...") Try ' Get the current transaction and roll it back. Dim trans As Transaction trans = Transaction.Current trans.Rollback() Catch ex As SqlException ' Catch the exception instead of throwing it. ' This allows the connection to close correctly. End Try End If ' Close the connection. connection.Close()End UsingEnd SubEnd Class
Язык Transact-SQL.
--Register assembly.CREATE ASSEMBLY TestProcs FROM 'C:\Programming\TestProcs.dll' GoCREATE PROCEDURE uspRollbackFromProc AS EXTERNAL NAME TestProcs.StoredProcedures.uspRollbackFromProcGo-- Execute procedure.BEGIN TRYBEGIN TRANSACTION -- Perform other actions.Exec uspRollbackFromProc-- Perform other actions.PRINT N'Commiting transaction...'COMMIT TRANSACTIONEND TRYBEGIN CATCHSELECT ERROR_NUMBER() AS ErrorNum, ERROR_MESSAGE() AS ErrorMessagePRINT N'Exception thrown, rolling back transaction.'ROLLBACK TRANSACTIONPRINT N'Transaction rolled back.' END CATCHGo-- Clean up.DROP Procedure uspRollbackFromProc;GoDROP ASSEMBLY TestProcs;Go