SQL原子事务不是原子的

时间:2012-08-27 00:00:43

标签: c# sql stored-procedures atomic

以下是我正在处理的两个存储过程的两次调用的代码。我以为我已设置为执行全部或全部原子事务,但是如果第二个存储过程调用中存在错误,则第一个仍将执行。我还是不熟悉用C#编写这些内容,所以任何帮助都会受到赞赏。 THX!

我还应该提一下我做的其他事情我发现很奇怪:如果抛出异常并且程序进入“catch”块,它仍然会在“finally”块中运行代码。我的理解是,如果抛出异常,“catch”块中的代码将被执行。

EDIT 感谢下面的回复,我修复了catch / finally混乱,并添加了一个ExecuteNonQuery()调用,因此第一个存储过程也被调用。但是,第一个存储过程在被调用时需要在第二个存储过程可以执行之前首先执行。这是否可以在原子事务中完成,还是必须单独运行?

try
{
    cm = Dts.Connections["serverName"];
    sqlConn = (SqlConnection)cm.AcquireConnection(Dts.Transaction);
    sqlTrans = sqlConn.BeginTransaction("QueueUpdates");                                


    if (dummyIndicator.Equals("Y"))
    {
        string temp = retrievedMessage.Substring(203, 17);
        int newNumber = (int)(long.Parse(temp) / 777);

        SqlParameter newNum = new SqlParameter("@newNum", num.Value);
        SqlParameter oldNum = new SqlParameter("@oldNum", newNumber);

        sqlComm = new SqlCommand("DB.dbo.sp_UpdateNumber", sqlConn, sqlTrans);
        sqlComm.CommandType = CommandType.StoredProcedure;
        sqlComm.Parameters.Add(newNum);
        sqlComm.Parameters.Add(oldNum);
        sqlComm.Transaction = sqlTrans;
        sqlComm.ExecuteNonQuery();

    }

    //Update records according to queue messages
    sqlComm = new SqlCommand("DB.dbo.sp_AgentIdAprCheck", sqlConn, sqlTrans);
    sqlComm.CommandType = CommandType.StoredProcedure;
    sqlComm.Parameters.Add(num);
    sqlComm.Parameters.Add(addOrUpdate);
    sqlComm.Parameters.Add(companyCode);
    sqlComm.Parameters.Add(agentID);
    sqlComm.Parameters.Add(firstName);
    sqlComm.Parameters.Add(lastName);
    sqlComm.Parameters.Add(suffix);
    sqlComm.Parameters.Add(taxIdType);
    sqlComm.Parameters.Add(entityType);
    sqlComm.Parameters.Add(corporateName);
    sqlComm.Parameters.Add(outNewNumber);
    sqlComm.Parameters.Add(outCurrentNumber);
    sqlComm.Parameters.Add(outOperator);
    sqlComm.Parameters.Add(outDate);
    sqlComm.Parameters.Add(returnVal);
    sqlComm.ExecuteNonQuery();

    sqlTrans.Commit();

    if (addOrUpd.Equals("ADD")){recordsAdded++;}
    else{recordsUpdated++;}

}
catch (Exception ex)
{
    _sqlDataErrors++;
    swLog.WriteLine("Message not updated: " + retrievedMessage);
    swLog.WriteLine("Error: " + ex.ToString());
    sqlTrans.Rollback();
}
finally
{                                
    cm.ReleaseConnection(sqlConn);
}

3 个答案:

答案 0 :(得分:1)

首先,为什么要强调transaction是原子的? Transaction表示总是原子的smth,任何事务保证提交所有块提交或不是单个块提交。

在您的情况下考虑事务的使用 - 这取决于您的域逻辑。选择很简单:

  • 如果只能在没有秒的情况下运行第一个sp,则不要使用 单笔交易。
  • 如果不可能 - 请使用它。

因此,如果您提供有关域规则和逻辑的进一步说明,我们可以说出最适合您的场景。

<强> UPD 好吧,在你解释了一下之后,我发现没有问题 - 确保你可以用特定的方式实现它并获得预期的行为,这样来自你们两个sp的命令将由单个事务传递并作为一个事件。 我还想指出可能改进的代码。

  1. 您可以使异常处理更具体。现在你在出现问题时回滚。例如 - 如果Dts.Connections["serverName"];null,则在sqlTrans初始化之前会出现NullReferenceException,然后在catch块中您将获得另一个NullReferenceException,处理sqlTrans,为空

  2. 您可以将代码拆分为特定于数据库的所有内容,只是为了确保该块中的异常始终是数据库层的异常。我的意思是做像

    这样的事情

    string temp = retrievedMessage.Substring(203, 17);

    int newNumber = (int)(long.Parse(temp) / 777);

  3. 在其他地方,因为它有潜在危险,可以抛出异常。

答案 1 :(得分:0)

  1. 你似乎没有真正执行第一个SP,所以我的猜测是第二个问题可能是由于那个?

  2. finally的目的是始终执行,无论代码块如何结束(正常退出,异常,返回语句等都会最终执行finally)。因此,它应该用于清理资源,而不是catch。将Commit()调用移至try块结束前的最后一行,并从ReleaseConnection块中删除catch,然后事情应该按预期开始工作

答案 2 :(得分:0)

最后执行块无论发生什么(发生异常或不发生)Control is always passed to the finally block regardless of how the try block exits.

将事务提交调用从finally块移动到ExecuteQuery调用之后。您还要在第一个存储过程的if块中创建一个sql命令对象,并将其保存在sqlComm中,但是在if块之外,您要为第二个存储过程创建另一个sql命令对象并将其保存在同一个{{1}中对象。好像你的第一个存储过程永远不会被执行。您可能需要更改那里的逻辑。