我遇到了问题,我发现的所有文章或例子似乎都不关心它。
我想在事务中执行一些数据库操作。我想做的与大多数例子非常相似:
using (SqlConnection Conn = new SqlConnection(_ConnectionString))
{
try
{
Conn.Open();
SqlTransaction Trans = Conn.BeginTransaction();
using (SqlCommand Com = new SqlCommand(ComText, Conn))
{
/* DB work */
}
}
catch (Exception Ex)
{
Trans.Rollback();
return -1;
}
}
但问题是SqlTransaction Trans
在try
块内声明。因此,catch()
块中无法访问它。大多数示例只是在Conn.Open()
块之前执行Conn.BeginTransaction()
和try
,但我认为这有点冒险,因为两者都会抛出多个异常。
我错了,还是大多数人都忽略了这种风险? 如果发生异常,能够回滚的最佳解决方案是什么?
答案 0 :(得分:56)
using (var Conn = new SqlConnection(_ConnectionString))
{
SqlTransaction trans = null;
try
{
Conn.Open();
trans = Conn.BeginTransaction();
using (SqlCommand Com = new SqlCommand(ComText, Conn, trans))
{
/* DB work */
}
trans.Commit();
}
catch (Exception Ex)
{
if (trans != null) trans.Rollback();
return -1;
}
}
或者你可以更清洁,更轻松地使用它:
using (var Conn = new SqlConnection(_ConnectionString))
{
try
{
Conn.Open();
using (var ts = new System.Transactions.TransactionScope())
{
using (SqlCommand Com = new SqlCommand(ComText, Conn))
{
/* DB work */
}
ts.Complete();
}
}
catch (Exception Ex)
{
return -1;
}
}
答案 1 :(得分:8)
我不喜欢键入类型并将变量设置为null,所以:
try
{
using (var conn = new SqlConnection(/* connection string or whatever */))
{
conn.Open();
using (var trans = conn.BeginTransaction())
{
try
{
using (var cmd = conn.CreateCommand())
{
cmd.Transaction = trans;
/* setup command type, text */
/* execute command */
}
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
/* log exception and the fact that rollback succeeded */
}
}
}
}
catch (Exception ex)
{
/* log or whatever */
}
如果你想切换到MySql或其他提供商,你只需修改1行。
答案 2 :(得分:6)
使用此
using (SqlConnection Conn = new SqlConnection(_ConnectionString))
{
SqlTransaction Trans = null;
try
{
Conn.Open();
Trans = Conn.BeginTransaction();
using (SqlCommand Com = new SqlCommand(ComText, Conn))
{
/* DB work */
}
}
catch (Exception Ex)
{
if (Trans != null)
Trans.Rollback();
return -1;
}
}
顺便说一句 - 你没有在成功处理的情况下提交
答案 3 :(得分:3)
using (SqlConnection Conn = new SqlConnection(_ConnectionString))
{
try
{
Conn.Open();
SqlTransaction Trans = Conn.BeginTransaction();
try
{
using (SqlCommand Com = new SqlCommand(ComText, Conn))
{
/* DB work */
}
}
catch (Exception TransEx)
{
Trans.Rollback();
return -1;
}
}
catch (Exception Ex)
{
return -1;
}
}
答案 4 :(得分:1)
Microsoft示例,将begin trans置于try / catch see this msdn link之外。我假设BeginTransaction方法应该抛出异常或者开始一个事务但是从不两者(尽管文档没有说这是不可能的)。
但是,您可能更善于使用TransactionScope来管理很多(不那么)繁重的工作:this link
答案 5 :(得分:1)
SqlConnection conn = null;
SqlTransaction trans = null;
try
{
conn = new SqlConnection(_ConnectionString);
conn.Open();
trans = conn.BeginTransaction();
/*
* DB WORK
*/
trans.Commit();
}
catch (Exception ex)
{
if (trans != null)
{
trans.Rollback();
}
return -1;
}
finally
{
if (conn != null)
{
conn.Close();
}
}
答案 6 :(得分:1)
当我在2018年底第一次发现这个问题时,我并不认为当时投票最高的答案可能存在错误,但是事实确实如此。我首先想到的只是简单地评论答案,然后我又想用自己的参考文献来支持我的主张。以及我所做的测试(基于.Net Framework 4.6.1和.Net Core 2.1。)
鉴于OP的约束,应该在连接内声明事务,这使我们可以使用其他答案中已经提到的2种不同的实现:
使用TransactionScope
using (SqlConnection conn = new SqlConnection(conn2))
{
try
{
conn.Open();
using (TransactionScope ts = new TransactionScope())
{
conn.EnlistTransaction(Transaction.Current);
using (SqlCommand command = new SqlCommand(query, conn))
{
command.ExecuteNonQuery();
//TESTING: throw new System.InvalidOperationException("Something bad happened.");
}
ts.Complete();
}
}
catch (Exception)
{
throw;
}
}
使用SqlTransaction
using (SqlConnection conn = new SqlConnection(conn3))
{
try
{
conn.Open();
using (SqlTransaction ts = conn.BeginTransaction())
{
using (SqlCommand command = new SqlCommand(query, conn, ts))
{
command.ExecuteNonQuery();
//TESTING: throw new System.InvalidOperationException("Something bad happened.");
}
ts.Commit();
}
}
catch (Exception)
{
throw;
}
}
您应该注意,在SqlConnection中声明TransactionScope时,未自动将连接对象加入事务,而必须使用conn.EnlistTransaction(Transaction.Current);
测试并证明
我已经在SQL Server数据库中准备了一个简单的表:
SELECT * FROM [staging].[TestTable]
Column1
-----------
1
.NET中的更新查询如下:
string query = @"UPDATE staging.TestTable
SET Column1 = 2";
在command.ExecuteNonQuery()之后,会引发异常:
command.ExecuteNonQuery();
throw new System.InvalidOperationException("Something bad happened.");
以下是完整的示例供您参考:
string query = @"UPDATE staging.TestTable
SET Column1 = 2";
using (SqlConnection conn = new SqlConnection(conn2))
{
try
{
conn.Open();
using (TransactionScope ts = new TransactionScope())
{
conn.EnlistTransaction(Transaction.Current);
using (SqlCommand command = new SqlCommand(query, conn))
{
command.ExecuteNonQuery();
throw new System.InvalidOperationException("Something bad happened.");
}
ts.Complete();
}
}
catch (Exception)
{
throw;
}
}
如果执行测试,则在TransactionScope完成之前将引发异常,并且更新不会应用于表(事务性回滚),并且值保持不变。这是每个人都期望的预期行为。
Column1
-----------
1
如果我们忘记使用conn.EnlistTransaction(Transaction.Current);
在交易中加入连接,会发生什么?
重新运行该示例会再次引发异常,并且执行流会立即跳转到catch块。尽管从未ts.Complete();
被调用,但表值已更改:
Column1
-----------
2
由于事务作用域是在SqlConnection之后声明的,因此连接不知道该作用域,也不会隐式加入所谓的ambient transaction。
对数据库书呆子的更深入分析
要深入研究,如果执行在command.ExecuteNonQuery();
之后且在引发异常之前暂停,我们可以在数据库(SQL Server)上查询事务,如下所示:
SELECT tst.session_id, tat.transaction_id, is_local, open_transaction_count, transaction_begin_time, dtc_state, dtc_status
FROM sys.dm_tran_session_transactions tst
LEFT JOIN sys.dm_tran_active_transactions tat
ON tst.transaction_id = tat.transaction_id
WHERE tst.session_id IN (SELECT session_id FROM sys.dm_exec_sessions WHERE program_name = 'TransactionScopeTest')
请注意,可以通过连接字符串中的“应用程序名称”属性来设置会话program_name: Application Name=TransactionScopeTest;
当前存在的交易正在展开:
session_id transaction_id is_local open_transaction_count transaction_begin_time dtc_state dtc_status
----------- -------------------- -------- ---------------------- ----------------------- ----------- -----------
113 6321722 1 1 2018-11-30 09:09:06.013 0 0
在没有conn.EnlistTransaction(Transaction.Current);
的情况下,没有事务绑定到活动连接,因此在事务上下文中不会发生更改:
session_id transaction_id is_local open_transaction_count transaction_begin_time dtc_state dtc_status
----------- -------------------- -------- ---------------------- ----------------------- ----------- -----------
说明.NET Framework与.NET Core
在使用.NET Core进行测试期间,我遇到了以下异常:
System.NotSupportedException: 'Enlisting in Ambient transactions is not supported.'
seems .NET Core(2.1.0)当前不支持TransactionScope方法,无论是在SqlConnection之前还是之后都初始化了Scope。