事务范围未回滚事务

13

这是我事务范围源代码的当前架构。第三次插入引发了.NET异常(不是SQL异常),并且没有回滚前面的两个插入语句。我做错了什么?

编辑:我从insert2和insert3中移除了try/catch。我还从insert1的try/catch中移除了异常处理实用程序,并替换成了“throw ex”。它仍然没有回滚事务。

编辑2:我在Insert3方法上添加了try/catch,并在catch语句中放置了一个“throw”。它仍然没有回滚事务。

更新:根据我收到的反馈,“SqlHelper”类使用SqlConnection对象建立与数据库的连接,然后创建一个SqlCommand对象,将CommandType属性设置为“StoredProcedure”,并调用SqlCommand的ExecuteNonQuery方法。

我也没有在当前连接字符串中添加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);
    }
}

我不想对你的开发技能产生怀疑,但是你是如何测试事务是否已经回滚呢?有可能事务正常工作,而你误解了结果。也许还有其他原因,我们/你正在走错方向吗? - Frustrating Developments
希望这可以帮到你:http://stackoverflow.com/questions/28191333/error-in-ambient-transaction-doesnt-rollback-the-transaction/28258935#28258935 - Khanh TO
6个回答

30

我也遇到了类似的问题。我的问题是因为在创建TransactionScope之前,我在SqlCommand中使用的SqlConnection已经打开了,所以它没有作为事务被注册到TransactionScope中。

SqlHelper类是否正在重用一个在进入TransactionScope块之前已经打开的SqlConnection实例?


6

看起来你是在Insert3()中捕获异常,所以你的代码在调用后继续执行。如果你想要回滚,你需要让异常上升到主例程中的try/catch块,这样ts.Complete()语句就不会被调用。


那么,我应该从插入2和3中移除try/catch语句吗? - Michael Kniskern
是的。或者重新抛出异常或另一个异常。 - tvanfosson
是的,你没有处理异常,调用方只是继续执行...另外,在sqlHelper中是否声明了任何事务?我曾经遇到过一个问题,我的helper中声明了一个事务,不得不将其删除。 - Saif Khan
不,我没有在 sqlhelper 类中声明任何事务。 - Michael Kniskern

1

如果在不调用ts.complete的情况下退出using语句,则只会发生隐式回滚。因为您正在Insert3()中处理异常,所以异常永远不会导致using语句退出。

要么重新抛出异常,要么通知调用者需要回滚(将Insert3()的签名更改为bool Insert3()?)


1

(基于不吞噬异常的编辑版本)

这些操作需要多长时间?如果其中任何一个操作非常耗时,那么可能是事务绑定错误特性已经影响到你了 - 即连接已经断开。尝试在连接字符串中添加Transaction Binding=Explicit Unbind


不行。我在连接字符串中添加了显式解绑,但它仍然无法回滚。 - Michael Kniskern

0

当我在TransactionScope中调用WCF服务操作时,我遇到了类似的问题。我注意到由于服务接口中的'TransactionFlow'属性,事务流不被允许。因此,WCF服务操作没有使用外部事务范围使用的事务。像下面展示的更改以允许事务流解决了我的问题。

[TransactionFlow(TransactionFlowOption.NotAllowed)]

[TransactionFlow(TransactionFlowOption.Allowed)]


0

我没有看到你的辅助类,但是如果你没有调用完成语句,即使从.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);

        }
    }

如果您想调试您的事务,您可以使用此脚本查看锁定和等待状态等信息。
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

默认隔离级别是可序列化的。您可以在此处阅读有关锁定和事务的更多信息


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接