如何确保在没有显式 ROLLBACK 的情况下回滚

How to ensure rollback from without explicit ROLLBACK

提问人:Steve S 提问时间:9/12/2023 最后编辑:CharliefaceSteve S 更新时间:9/13/2023 访问量:61

问:

如果由于 Catch 块而导致 ROLLBACK 超出范围,假设这是原因,除了将所有保存放在一个方法中之外,我应该如何解决?

SqlConnection connection = new SqlConnection(DataAccess.ConnectionString);
connection.Open();
string sql = "USE IncidentReports; BEGIN TRANSACTION;";
SqlCommand cmd = new SqlCommand(sql, connection);
cmd.ExecuteNonQuery();
cmd.Dispose();

try
{
    //if anyone of these saves fails, I want to rollback everything. No partial saves
    SaveIncident(connection);
    if (SelectedStatus.Key != _previousStatus) { SaveStatusChange(connection); }
    SaveEmployeeData(connection);
    SaveNotes(connection);
    SaveChecklist(connection);
    SaveExpenses(connection);

    _previousStatus = SelectedStatus.Key;

    HasBeenSaved = true;
    NewIncident = false;

    sql = "USE IncidentReports; COMMIT TRANSACTION;";
    cmd = new SqlCommand(sql, connection);
    cmd.ExecuteNonQuery();
    cmd.Dispose();
}
catch (Exception ex)
{
    sql = "USE IncidentReports; ROLLBACK TRANSACTION;";
    cmd = new SqlCommand(sql, connection);
    cmd.ExecuteNonQuery();
    cmd.Dispose();

    var mbp2 = new MessageBoxParams()
    {
        Caption = StaticGlobals.MSGBOX_CAPTION,
        Image = MessageBoxImage.Exclamation,
        Button = MessageBoxButton.OK,
        Message = $"Incident was not saved, the following error occurred;{Environment.NewLine}{ex.Message}"
    };
    ShowMessageBox(mbp2);
    return;
}
finally
{
    cmd?.Dispose();
    connection?.Close();
}           
C# SQL 事务范围 SQLAlient

评论

0赞 Matteo Umili 9/12/2023
c-sharpcorner.com/UploadFile/87b416/sql-transaction 阅读有关“与 ADO.NET 的交易”的部分

答:

3赞 Charlieface 9/12/2023 #1

您不应该手动执行 和 的事务,除非它们都在同一个批次中(以及 )。BEGINCOMMITSET XACT_ABORT ON

如果你想要一个客户端事务(即你希望事务在任何事务之前开始,并继续直到所有命令完成),你应该只在对象上使用。SqlCommandBeginTransactionSqlConnection

然后,使用该对象创建每个命令。最后你打电话。SqlTransactiontransaction.Commit();

此外:

  • 在连接、命令和事务对象上放置一个。那么你就不需要 .usingfinally
  • 而不是 ,将要连接的数据库放入连接字符串中。USE
  • 在关闭连接之前,不要使用消息框阻止调用线程。
  • 请考虑使用 以保持 UI 响应。asyncawait
try
{
    using var connection = new SqlConnection(DataAccess.ConnectionString);
    connection.Open();
    using var tran = connection.BeginTransaction();

    SaveIncident(connection, tran);

    if (SelectedStatus.Key != _previousStatus)
    {
        SaveStatusChange(connection, tran);
    }

    SaveEmployeeData(connection, tran);
    SaveNotes(connection, tran);
    SaveChecklist(connection, tran);
    SaveExpenses(connection, tran);

    _previousStatus = SelectedStatus.Key;

    HasBeenSaved = true;
    NewIncident = false;

    tran.Commit();
}
catch (Exception ex)
{
    var mbp2 = new MessageBoxParams()
    {
        Caption = StaticGlobals.MSGBOX_CAPTION,
        Image = MessageBoxImage.Exclamation,
        Button = MessageBoxButton.OK,
        Message = $"Incident was not saved, the following error occurred;{Environment.NewLine}{ex.Message}"
    };
    ShowMessageBox(mbp2);
    return;
}

评论

0赞 Steve S 9/14/2023
“ShowMessageBox()”是 UI 线程正在侦听的事件。我已经采纳了你的其他建议。“USE <database>”只是在SSMS中工作的残余,我连接到多个数据库。它位于连接字符串中,因此是冗余的。谢谢。
2赞 MoseLey 9/12/2023 #2

您可以创建数据库事务对象

var transaction = connection.BeginTransaction();

并将事务引用到每个 SqlCommand 的 Transaction 属性

command.Transaction = transaction;

手术后,您可以使用

transaction.Commit();
or
transaction.Rollback();

或者,可以在 SQL 查询中使用条件回滚

IF @@TRANCOUNT > 0
    ROLLBACK TRANSACTION;

我不确定,但您可以尝试为您的交易提供别名,例如

BEGIN TRANSACTION A
COMMIT TRANSACTION A
ROLLBACK TRANSACTION A

如果没有帮助,您可以检查一下。

如何在 C 中使用 SqlTransaction#