Корпоративная библиотека: откат нескольких транзакций - PullRequest
2 голосов
/ 04 марта 2011

В настоящее время я использую Microsoft Enterprise Library 5.0, и мне интересно, является ли приведенный ниже код приемлемым способом обработки транзакций.

Я немного упростил сценарий, но суть в том, что я хочу выполнить несколько вставок в разные базы данных в рамках одной транзакции.

Если какая-либо из вставок не удалась, все транзакции следует откатить.

Я посмотрел на TransactionScope, но я хочу знать, смогу ли я обойтись без него.

public void InsertStuff_AcrossDbs()
{
    //Create a ref to 2 different Db's on the same server
    Database db_a = DatabaseFactory.CreateDatabase("Data Source=localhost;Initial Catalog=db_a"); 
    Database db_b = DatabaseFactory.CreateDatabase("Data Source=localhost;Initial Catalog=db_b");

    //Create Connections for the 2 db's
    using (DbConnection connection_db_a = db_a.CreateConnection())
    using (DbConnection connection_db_b = db_b.CreateConnection())
    {
        //Create DbTransactions for the 2 db's
        DbTransaction transaction_dbA = connection_db_a.BeginTransaction();
        DbTransaction transaction_dbB = connection_db_b.BeginTransaction();

        try
        {
            //Insert into DbA.Person and get the PK
            DbCommand dbCmd_dbA_insert = db_a.GetSqlStringCommand("Insert INTO Person(Name,Age)Values('test',23); SET @pkReturnId= SCOPE_IDENTITY() ");
            db_a.AddOutParameter(dbCmd_dbA_insert, "pkReturnId", DbType.Int32, 0);
            db_a.ExecuteNonQuery(dbCmd_dbA_insert, transaction_dbA);
            int personID = Convert.ToInt32(db_a.GetParameterValue(dbCmd_dbA_insert, "@pkReturnId"));

            //Insert 'personId' into dbB.Employee  (a different table in a different db)
            DbCommand dbCmd_dbB_delete = db_a.GetSqlStringCommand("Insert INTO Employee(personId) VALUES(" + personID + ")");
            db_a.ExecuteNonQuery(dbCmd_dbB_delete, transaction_dbB);

            //try to commit both transactions
            transaction_dbA.Commit();
            transaction_dbB.Commit();
        }
        catch (Exception ex)
        {
            //If either transactions fails, roll back both
            try
            {
                transaction_dbA.Rollback();
            }
            catch { }

            try
            {
                transaction_dbB.Rollback();
            }
            catch { }

            throw ex;
        }
        finally
        {
            connection_db_a.Close();
            connection_db_b.Close();
        }

    }

}

Считается ли этот код нормальным или будут сценарии, когда не все транзакции будут отменены, если одна из них выдаст ошибку?

1 Ответ

3 голосов
/ 05 марта 2011

Ваш код не ОК.Существуют сценарии, в которых две базы данных не будут согласованы.

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

например,

Database db_a = DatabaseFactory.CreateDatabase("Data Source=localhost;Initial Catalog=db_a"); 
Database db_b = DatabaseFactory.CreateDatabase("Data Source=localhost;Initial Catalog=db_b");

using (TransactionScope scope = new TransactionScope())
{
    using (DbConnection connA = db_a.CreateConnection())
    {
        // ...
    }

    using (DbConnection connB = db_b.CreateConnection())
    {
        // ...
    }

    scope.Complete();
}

Я понимаю, что ваш код - упрощенный сценарий, поэтому я не уверен, применимы ли они к вашему фактическому коду, но вот некоторыедругие комментарии:

DbCommand dbCmd_dbB_delete = 
    db_a.GetSqlStringCommand("Insert INTO Employee(personId) VALUES(" + personID + ")");

Динамические строки SQL следует избегать.Они уязвимы для атак с использованием SQL-инъекций, а также снижают производительность, поскольку план выполнения базы данных не будет повторно использоваться для других запросов с другими параметрами.

finally
{
    connection_db_a.Close();
    connection_db_b.Close();
}

Вам не нужно вручную закрывать соединения, поскольку вы утилизируетесоединения через ваши операторы использования и Close и Dispose функционально эквивалентны.

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