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

nod*_*ots 5 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这里是可以运行的可运行的演示代码(需要一个名为"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();
    }
}
Run Code Online (Sandbox Code Playgroud)

Ada*_*rth 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
Run Code Online (Sandbox Code Playgroud)

这适用于 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您尝试这样做,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();
            }
        }
Run Code Online (Sandbox Code Playgroud)

它将按您的预期工作。

请注意,始终处置实现的对象IDisposable- 最好在using声明中。

进一步阅读:

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

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