Насколько я понимаю, C # TransactionScope
все еще может работать при переносе T-SQL BEGIN / COMMIT TRANSACTION
в хранимую процедуру.
У меня есть следующий метод C #, который сначала выполняет EF Save
, затем вызывает хранимую процедуру, которая имеет свою собственную транзакцию, а затем вызывает внешнюю службу по HTTP
public async Task DoSomething(MyDto dto)
{
using (var scope = new TransactionScope())
{
//Save First
var myEntity = await _dbContext.MyEntity.Where(x=>x.Id == dto.Id).SingleOtDefaultAsync();
// Assign properties here from dto to MyEntity and then save entity
await _dbContext.SaveChangesAsync();
// call stored procedure that has its own transaction
_dbContext.prcDoExtraWork(dto.Id);
// call external service using Http
await _httpClient.PostAsync(url,somecontent)
scope.Complete();
}
}
Хранимая процедура:
CREATE PROCEDURE [dbo].[prcDoExtraWork]
@ID INT
AS
BEGIN
SET NOCOUNT ON;
SET ANSI_WARNINGS ON;
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION
// modify data and inserts records into tables
COMMIT TRANSACTION
SELECT 1 AS `Result`
END TRY
BEGIN CATCH
IF (XACT_STATE() <> 0)
BEGIN
ROLLBACK TRANSACTION
IF @ErrorMessage IS NULL
BEGIN
SET @ProcName = ERROR_PROCEDURE();
SET @ErrorMessage = ERROR_MESSAGE();
SET @ErrorNumber = ERROR_NUMBER();
SET @ErrorSeverity = ERROR_SEVERITY();
SET @ErrorState = ERROR_STATE();
END
EXEC prcErrorHandler @ProcName = @ProcName,
@ErrorMessage = @ErrorMessage,
@ErrorSeverity = @ErrorSeverity,
@ErrorState = @ErrorState,
@ErrorNumber = @ErrorNumber
SELECT 0 AS `Result`
END
END CATCH
SET XACT_ABORT OFF;
END
Проблема 1: вызов внешней службы по протоколу http завершается неудачно, я ожидал, что любая хранимая процедура записи, вставленная или измененная, будет ROLLBACK.
Однако этого не происходит. Я до сих пор вижу новые записи в базе данных
Выпуск 2
Чтобы устранить вышеуказанную ошибку, мне пришлось включить TransactionScopeAsyncFlowOption, поскольку я использую асинхронные методы
using (var scope = new TransactionScope(TransactionScopeOption.Required, TransactionScopeAsyncFlowOption.Enabled))
{
// do your stuff
scope.complete();
}
Однако теперь на scope.complete () я получаю ошибку
Операция транзакции не может быть выполнена, потому что есть
ожидающие запросы, работающие над этой транзакцией
System.Transactions.TransactionAbortedException: транзакция имеет
прервана. ---> System.Data.SqlClient.SqlException: транзакция
операция не может быть выполнена, потому что есть ожидающие запросы
работает над этой транзакцией. в
System.Data.SqlClient.SqlConnection.OnError (исключение SqlException,
Boolean breakConnection, Action`1 wrapCloseInAction) в
System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning (TdsParserStateObject
stateObj, Boolean CallerHasConnectionLock, Boolean asyncClose) в
System.Data.SqlClient.TdsParser.TryRun (RunBehavior runBehavior,
SqlCommand cmdHandler, SqlDataReader dataStream,
BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject
stateObj, Boolean & dataReady) в
System.Data.SqlClient.TdsParser.Run (RunBehavior runBehavior,
SqlCommand cmdHandler, SqlDataReader dataStream,
BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject
stateObj) в
System.Data.SqlClient.TdsParser.TdsExecuteTransactionManagerRequest (байт []
буфер, запрос TransactionManagerRequestType, строковое имя транзакции,
TransactionManagerIsolationLevel isoLevel, время ожидания Int32,
Транзакция SqlInternalTransaction, TdsParserStateObject stateObj,
Boolean isDelegateControlRequest) в
System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransactionYukon (TransactionRequest
транзакционный запрос, строковое имя транзакции, IsolationLevel iso,
SqlInternalTransaction internalTransaction, Boolean
isDelegateControlRequest) в
System.Data.SqlClient.SqlDelegatedTransaction.SinglePhaseCommit (SinglePhaseEnlistment
enlistment) --- конец трассировки стека внутренних исключений --- at
System.Transactions.TransactionStateAborted.EndCommit (InternalTransaction
tx) в System.Transactions.CommittableTransaction.Commit () в
System.Transactions.TransactionScope.InternalDispose () в
System.Transactions.TransactionScope.Dispose () в
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.d__343.MoveNext ()
--- Конец стека трассировки из предыдущего места, где было сгенерировано исключение --- в
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () в
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (Task
задача) в XXXXXXXXXXXXX.MyDetailController.d__9.MoveNext ()
--- Конец стека трассировки из предыдущего места, где было сгенерировано исключение --- в
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () в
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (Task
задача) в
System.Web.Mvc.Async.TaskAsyncActionDescriptor.EndExecute (IAsyncResult
asyncResult) в
System.Web.Mvc.Async.AsyncControllerActionInvoker. <> C__DisplayClass37.b__36 (IAsyncResult
asyncResult) в
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod (IAsyncResult
asyncResult) в
System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.b__3d ()
вSystem.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters. <> C__DisplayClass46.b__3f ()
в
System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters. <> C__DisplayClass46.b__3f ()
в
System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters. <> C__DisplayClass46.b__3f ()
в
System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters (IAsyncResult
asyncResult) в
System.Web.Mvc.Async.AsyncControllerActionInvoker. <> C__DisplayClass21. <> C__DisplayClass2b.b__1c ()
в
System.Web.Mvc.Async.AsyncControllerActionInvoker. <> C__DisplayClass21.b__1e (IAsyncResult
AsyncResult)