在失败的ALTER TABLE ... ADD CONSTRAINT上回滚事务到保存点

时间:2011-03-15 16:10:25

标签: c# sql-server-2005

在故障回滚到上一个保存点(而不是回滚整个事务)的情况下,有没有办法在事务中添加检查约束?

在我的情况下,当ALTER TABLE ... ADD CONSTRAINT命令失败时,事务无法回滚到保存点(尝试这样做会抛出InvalidOperationException)。

概述以证明关键点:

SqlTransaction transaction = connection.BeginTransaction();

// ... execute SQL commands on the transaction ...

// Create savepoint
transaction.Save("mySavepoint");

try
{
    // This will fail...
    SqlCommand boom = new SqlCommand(
        "ALTER TABLE table WITH CHECK ADD CONSTRAINT ...", 
        connection, 
        transaction);

    boom.ExecuteNonQuery();
}
catch
{
    // ...and should be rolled back to the savepoint, but can't.
    try
    {
        transaction.Rollback("mySavepoint");
    }
    catch (InvalidOperationException)
    {
        // Instead, an InvalidOperationException is thrown.
        // The transaction is unusable and can only be rolled back entirely.
        transaction.Rollback();
    }
}

这里有准备运行的演示代码进行测试(你需要一个名为“test”的数据集):

public class Demo
{
    private const string _connectionString = "Data Source=(local);Integrated security=true;Initial Catalog=test;";
    private const string _savepoint = "save";
    private static readonly string _tableName = DateTime.Now.ToString("hhmmss");
    private static readonly string _constraintName = "CK" + DateTime.Now.ToString("hhmmss");

    private static readonly string _createTable = "CREATE TABLE [dbo].[" + _tableName + "] ([one] [int] NULL,[two] [int] NULL) ON [PRIMARY]";
    private static readonly string _insert1 = "INSERT INTO [" + _tableName + "] VALUES (1,1)";
    private static readonly string _addConstraint = "ALTER TABLE [dbo].[" + _tableName + "] WITH CHECK ADD  CONSTRAINT [" + _constraintName + "] CHECK (([one]>(1)))";
    private static readonly string _insert2 = "INSERT INTO [" + _tableName + "] VALUES (2,2)";


    public static void Main(string[] args)
    {
        // Example code! Please ignore missing using statements.

        SqlConnection connection = new SqlConnection(_connectionString);
        connection.Open();

        SqlTransaction transaction = connection.BeginTransaction();

        SqlCommand createTable = new SqlCommand(_createTable, connection, transaction);
        createTable.ExecuteNonQuery();

        // Create savepoint
        transaction.Save(_savepoint);

        SqlCommand insert1 = new SqlCommand(_insert1, connection, transaction);
        insert1.ExecuteNonQuery();

        try
        {
            // This will fail...
            SqlCommand boom = new SqlCommand(_addConstraint, connection, transaction);
            boom.ExecuteNonQuery();
        }
        catch
        {
            // ...and should be rolled back to the savepoint, but can't
            transaction.Rollback(_savepoint);
        }

        SqlCommand insert2 = new SqlCommand(_insert2, connection, transaction);
        insert2.ExecuteNonQuery();

        transaction.Commit();
        connection.Close();
    }
}

2 个答案:

答案 0 :(得分:1)

当我尝试使用TSQL时,我得到了同样的行为。

BEGIN TRAN

CREATE TABLE foo (col int)

INSERT INTO foo values (1)

SAVE TRANSACTION ProcedureSave;

BEGIN TRY
ALTER TABLE foo WITH CHECK ADD  CONSTRAINT ck CHECK (col= 2)
END TRY
BEGIN CATCH
    SELECT XACT_STATE() AS XACT_STATE
    /*Returns -1, transaction is uncommittable. Next line will fail*/

    ROLLBACK TRANSACTION ProcedureSave 
   /*Msg 3931, Level 16, State 1: The current transaction cannot be committed and
   cannot be rolled back to a savepoint. Roll back the entire transaction.*/
END CATCH

GO

SELECT @@TRANCOUNT AS [@@TRANCOUNT] /*Zero the transaction was rolled back*/

我没有在文档中找到任何信息,说明哪些错误会导致交易以这种方式注定失败。我认为this connect item comment没有这样的文件。

  

答案是,错误处理是   逐案。这不仅取决于   服务器,还有错误类型   和背景。不幸的是,有   没有公布的错误处理列表   不同错误的行为。在   一般来说,只有严重错误才应该   杀死连接和极端的连接   关机服务器。但是当涉及到   语句中止与事务中止,   很难总结规则 -   即具体情况。

答案 1 :(得分:0)

我认为你不能在脚本和C#中混合使用保存点。我执行以下SQL:

BEGIN TRANSACTION

INSERT INTO Foos (Fooname)
VALUES ('Bar1')

SAVE TRANSACTION MySavePoint;

INSERT INTO Foos (FooName)
VALUES ('Bar2')

ROLLBACK TRANSACTION MySavePoint

COMMIT TRANSACTION

这将在SQL中使用,并将使用以下代码:

using (SqlConnection conn = new SqlConnection("connectionString"))
{
    conn.Open();

    using (SqlTransaction trans = conn.BeginTransaction())
    using (SqlCommand comm = new SqlCommand("The Above SQL", conn, trans))
    {
        comm.ExecuteNonQuery();
        trans.Commit();
    }
}

如果您尝试trans.Rollback("MySavePoint");它将失败,因为trans对象无法控制保存点 - 它不知道它。

如果将SQL拆分为两个独立插入并使用以下代码:

using (SqlConnection conn = new SqlConnection("connectionString"))
        {
            conn.Open();

            using (SqlTransaction trans = conn.BeginTransaction())
            using (SqlCommand comm1 = new SqlCommand("INSERT INTO Foos(fooName) VALUES('Bar1')", conn, trans))
            using (SqlCommand comm2 = new SqlCommand("INSERT INTO Foos(fooName) VALUES('Bar2')", conn, trans))
            {
                comm1.ExecuteNonQuery();
                trans.Save("MySavePoint");
                comm2.ExecuteNonQuery();
                trans.Rollback("MySavePoint");
                trans.Commit();
            }
        }

它会按预期工作。

只需注意,始终处理实现IDisposable的对象 - 最好是在using语句中。

进一步阅读:

http://www.davidhayden.com/blog/dave/archive/2005/10/15/2517.aspx

更新:在使用您的示例代码执行了一段时间后,看起来由于来自SQL的错误,事务正在回滚并变得无法使用。正如在另一个答案中所说的那样,似乎与SQL一起,由于某些错误,无论保存点如何,事务都被强制回滚。唯一可靠的方法是重新排序针对数据库运行的命令,而不是依赖保存点,或者至少不依赖于保存点中的操作。