TransactionScope не откатывает транзакцию
Вот текущая архитектура исходного кода транзакции. Третья вставка генерирует исключение .NET(не исключение SQL) и не откатывает два предыдущих оператора вставки. Что я делаю неправильно?
EDIT: Я удалил try/catch из insert2 и insert3. Я также удалил утилиту обработки исключений из try/catch insert1 и поставил "throw ex". Он по-прежнему не откатывает транзакцию.
EDIT 2: Я добавил попытку/уловить метод Insert3 и просто поставлю "throw" в выводе. Он по-прежнему не откатывает транзакцию.
ОБНОВЛЕНИЕ: Основываясь на полученной обратной связи, класс "SqlHelper" использует объект SqlConnection для установления соединения с базой данных, затем создает объект SqlCommand, задает свойство CommandType "StoredProcedure" "и вызывает метод ExecuteNonQuery для SqlCommand.
Я также не добавил Transaction Binding = Explicit Unbind в текущую строку подключения. Я добавлю, что во время моего следующего теста.
public void InsertStuff()
{
try
{
using(TransactionScope ts = new TransactionScope())
{
//perform insert 1
using(SqlHelper sh = new SqlHelper())
{
SqlParameter[] sp = { /* create parameters for first insert */ };
sh.Insert("MyInsert1", sp);
}
//perform insert 2
this.Insert2();
//perform insert 3 - breaks here!!!!!
this.Insert3();
ts.Complete();
}
}
catch(Exception ex)
{
throw ex;
}
}
public void Insert2()
{
//perform insert 2
using(SqlHelper sh = new SqlHelper())
{
SqlParameter[] sp = { /* create parameters for second insert */ };
sh.Insert("MyInsert2", sp);
}
}
public void Insert3()
{
//perform insert 3
using(SqlHelper sh = new SqlHelper())
{
SqlParameter[] sp = { /*create parameters for third insert */ };
sh.Insert("MyInsert3", sp);
}
}
Ответы
Ответ 1
У меня также возникла аналогичная проблема. Моя проблема возникла из-за того, что SqlConnection, который я использовал в моих SqlCommands, уже был открыт до создания TransactionScope, поэтому он никогда не попадал в TransactionScope в качестве транзакции.
Возможно ли, что класс SqlHelper повторно использует экземпляр SqlConnection, который открыт до того, как вы введете свой блок TransactionScope?
Ответ 2
Похоже, вы поймаете исключение в Insert3(), чтобы ваш код продолжался после вызова. Если вы хотите откат, вам нужно разрешить исключение в блок try/catch в основной подпрограмме, чтобы оператор ts.Complete() никогда не вызывался.
Ответ 3
Неявный откат будет происходить только в том случае, если использование выполняется без вызова ts.complete. Поскольку вы обрабатываете исключение в Insert3(), исключение никогда не приводит к выходу оператора using.
Либо переименуйте исключение, либо уведомите вызывающего, что требуется откат (чтобы изменить подпись Insert3() на bool Insert3()?)
Ответ 4
(на основе отредактированной версии, которая не проглатывает исключения)
Как долго выполняются операции? Если какой-либо из них очень длинный, возможно, что функция Связывание транзакций укусила вас - т.е. Соединение стало отсоединенным. Попробуйте добавить Transaction Binding=Explicit Unbind
в строку подключения.
Ответ 5
Я не вижу ваш вспомогательный класс, но область транзакций rollsback, если вы не вызываете полную инструкцию, даже если вы получаете ошибку из кода .NET. Я скопировал один пример для вас. Возможно, вы ошибаетесь в отладке. В этом примере есть ошибка в .net-коде и подобном блоке catch как ваш.
private static readonly string _connectionString = ConnectionString.GetDbConnection();
private const string inserttStr = @"INSERT INTO dbo.testTable (col1) VALUES(@test);";
/// <summary>
/// Execute command on DBMS.
/// </summary>
/// <param name="command">Command to execute.</param>
private void ExecuteNonQuery(IDbCommand command)
{
if (command == null)
throw new ArgumentNullException("Parameter 'command' can't be null!");
using (IDbConnection connection = new SqlConnection(_connectionString))
{
command.Connection = connection;
connection.Open();
command.ExecuteNonQuery();
}
}
public void FirstMethod()
{
IDbCommand command = new SqlCommand(inserttStr);
command.Parameters.Add(new SqlParameter("@test", "Hello1"));
ExecuteNonQuery(command);
}
public void SecondMethod()
{
IDbCommand command = new SqlCommand(inserttStr);
command.Parameters.Add(new SqlParameter("@test", "Hello2"));
ExecuteNonQuery(command);
}
public void ThirdMethodCauseNetException()
{
IDbCommand command = new SqlCommand(inserttStr);
command.Parameters.Add(new SqlParameter("@test", "Hello3"));
ExecuteNonQuery(command);
int a = 0;
int b = 1/a;
}
public void MainWrap()
{
TransactionOptions tso = new TransactionOptions();
tso.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
//TransactionScopeOption.Required, tso
try
{
using (TransactionScope sc = new TransactionScope())
{
FirstMethod();
SecondMethod();
ThirdMethodCauseNetException();
sc.Complete();
}
}
catch (Exception ex)
{
logger.ErrorException("eee ",ex);
}
}
Если вы хотите отлаживать транзакции, вы можете использовать этот script, чтобы увидеть блокировки и состояние ожидания и т.д.
SELECT
request_session_id AS spid,
CASE transaction_isolation_level
WHEN 0 THEN 'Unspecified'
WHEN 1 THEN 'ReadUncomitted'
WHEN 2 THEN 'Readcomitted'
WHEN 3 THEN 'Repeatable'
WHEN 4 THEN 'Serializable'
WHEN 5 THEN 'Snapshot' END AS TRANSACTION_ISOLATION_LEVEL ,
resource_type AS restype,
resource_database_id AS dbid,
DB_NAME(resource_database_id) as DBNAME,
resource_description AS res,
resource_associated_entity_id AS resid,
CASE
when resource_type = 'OBJECT' then OBJECT_NAME( resource_associated_entity_id)
ELSE 'N/A'
END as ObjectName,
request_mode AS mode,
request_status AS status
FROM sys.dm_tran_locks l
left join sys.dm_exec_sessions s on l.request_session_id = s.session_id
where resource_database_id = 24
order by spid, restype, dbname;
Вы увидите один SPID для двух вызовов метода перед вызовом метода исключений.
![two calls before exception]()
Уровень изоляции по умолчанию является сериализуемым. Здесь вы можете узнать больше о блокировках и транзакциях
Ответ 6
Я столкнулся с подобной проблемой, когда у меня был вызов операции службы WCF в TransactionScope
.
Я заметил, что поток транзакций не был разрешен из-за атрибута "TransactionFlow" в интерфейсе сервиса. Поэтому операция службы WCF не использовала транзакцию, используемую внешней областью транзакции. Изменение разрешения транзакции, как показано ниже, решило мою проблему.
[TransactionFlow(TransactionFlowOption.NotAllowed)]
в
[TransactionFlow(TransactionFlowOption.Allowed)]