Откат транзакции до точки сохранения при сбое ALTER TABLE ... ADD CONSTRAINT - PullRequest
5 голосов
/ 15 марта 2011

Есть ли способ добавить проверочное ограничение в транзакцию и в случае сбоя отката к предыдущей точке сохранения (вместо отката всей транзакции)?

В моем случае, когда команда 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 ]

1 голос
/ 16 марта 2011

Я получаю такое же поведение при попытке в 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*/

Я не нашел в документах никакой информации о том, какие ошибки могут привести к тому, что транзакция будет обречена таким образом. Я думаю, что не существует такой документации от комментарий к этому элементу соединения .

Ответ: обработка ошибок От случая к случаю. Это зависит не только от серверность, а также тип ошибки и контекст. К сожалению, есть нет опубликованного списка обработки ошибок поведение для разных ошибок. В Вообще, только ошибки должны убить связь и чрезвычайно выключение сервера. Но когда дело доходит до Отмена оператора против транзакции, трудно обобщить правила - то есть это в каждом конкретном случае.

0 голосов
/ 15 марта 2011

Я не думаю, что вы можете смешивать использование точек сохранения в скриптах и ​​в 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 транзакция принудительно откатывается независимо от точек сохранения из-за определенных ошибок. Единственный выход для этого - переупорядочить команды, запущенные для базы данных, и не полагаться на точки сохранения, или, по крайней мере, не полагаться на то, что это действие находится в точке сохранения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...