Интеграция System.Transactions с SQL Server

Применимо: платформа .NET Framework .NET Standard

Скачать ADO.NET

Платформа .NET включает новую платформу транзакций, к которой можно обращаться через пространство имен System.Transactions. Эта платформа предоставляет транзакции в полностью интегрированном с .NET виде, включая ADO.NET.

Помимо улучшенных способов программирования, System.Transactions и ADO.NET могут работать вместе для координации оптимизации при работе с транзакциями. Повышаемая транзакция — это упрощенная (локальная) транзакция, которая по необходимости может быть автоматически повышена до полностью распределенной транзакции.

Поставщик данных Microsoft SqlClient для SQL Server поддерживает повышаемые транзакций при работе с SQL Server. Повышаемая транзакция не вызывает дополнительную нагрузку распределенной транзакции, если таковая не требуется. Повышаемые транзакции выполняются автоматически и не требуют вмешательства разработчика.

Создание повышаемых транзакций

Поставщик данных Microsoft SqlClient для SQL Server обеспечивает поддержку повышаемых транзакций, которые обрабатываются с помощью классов в пространстве имен System.Transactions. Повышаемые транзакции оптимизируют работу с распределенными транзакциями за счет отсрочки создания распределенной транзакции до того момента, когда она будет необходима. Если требуется только один диспетчер ресурсов, то распределенная транзакция не создается.

Примечание.

В частично доверенном сценарии при повышении транзакции до распределенной требуется право доступа DistributedTransactionPermission .

Сценарии применения повышаемых транзакций

Как правило, для распределенных транзакций требуются значительные системные ресурсы, поскольку они управляются координатором распределенных транзакций (Майкрософт), который интегрирует все диспетчеры ресурсов, используемые в транзакции. Повышаемая транзакция — это особая форма транзакции System.Transactions, эффективно делегирующая работу простой транзакции SQL Server. System.Transactions, Microsoft.Data.SqlClient и SQL Server координируют работу, выполняемую при обработке транзакции, при необходимости повышая ее до полностью распределенной транзакции.

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

Ключевые слова в строке подключения

В свойстве ConnectionString поддерживается использование ключевого слова Enlist, которое указывает, будет ли клиент Microsoft.Data.SqlClient обнаруживать контексты транзакций и автоматически прикреплять соединение к распределенной транзакции. Если Enlist=true, то соединение автоматически прикрепляется к текущему контексту транзакции открывающего потока. Если Enlist=false, то соединение SqlClient не будет взаимодействовать с распределенной транзакцией. Значение Enlist по умолчанию - true. Если ключевое слово Enlist в строке соединения не задано, то соединение автоматически прикрепляется к распределенной транзакции, если она будет обнаружена при открытии соединения.

Ключевые слова Transaction Binding в строке соединения SqlConnection управляют связью соединения с прикрепленной транзакцией System.Transactions . Эта связь также доступна через свойство TransactionBinding построителя SqlConnectionStringBuilder.

В следующей таблице описаны возможные значения.

Ключевое слово Description
Implicit Unbind По умолчанию. Соединение отсоединяется от транзакции по ее завершении, вновь переходя в режим автоматической фиксации.
Explicit Unbind Соединение остается прикрепленным к транзакции до ее закрытия. Соединение будет потеряно, если связанная с ним транзакция не активна или не соответствует Current.

Использование класса TransactionScope

Класс TransactionScope делает блок кода транзакционным, неявно прикрепляя соединения к распределенной транзакции. В конце блока Complete перед тем, как выйти из него, необходимо вызвать метод TransactionScope . При выходе из этого блока вызывается метод Dispose . При возникновении исключения, в результате которого исполнение кода выходит за пределы области, транзакция считается прерванной.

Рекомендуется использовать блок using , чтобы гарантировать вызов метода Dispose для объекта TransactionScope при выходе из блока using. Неудачная попытка зафиксировать или откатить незавершенные транзакции может значительно снизить производительность, поскольку время ожидания по умолчанию для объекта TransactionScope составляет одну минуту. Если инструкция using не используется, необходимо явно выполнить все действия в блоке Try и вызвать метод Dispose в блоке Finally.

Если в объекте TransactionScopeвозникает исключение, транзакция помечается как несогласованная и прерывается. При удалении объекта TransactionScope будет произведен ее откат. Если исключений не возникает, то участвующие транзакции будут зафиксированы.

Примечание.

Класс TransactionScope создает транзакцию с уровнем изоляции IsolationLevel, равным Serializable по умолчанию. В зависимости от приложения уровень изоляции можно понижать во избежание большого количества состязаний данных в приложении.

Примечание.

В рамках распределенных транзакций рекомендуется выполнять только операции обновления, вставки и удаления, поскольку они потребляют значительные ресурсы базы данных. Инструкции выборки могут без необходимости блокировать ресурсы базы данных, и в некоторых случаях для выборки может потребоваться использование транзакций. Любые не связанные с базой данных действия должны выполняться за пределами области транзакции, если только в ней не задействованы другие диспетчеры ресурсов транзакций. Хотя исключение в области транзакции не позволяет зафиксировать транзакцию, класс TransactionScope не имеет возможности отката каких-либо изменений, внесенных кодом за пределами области самой транзакции. При необходимости предпринять какие-либо действия, когда производится откат транзакции, следует написать собственную реализацию интерфейса IEnlistmentNotification и явно прикрепить его к транзакции.

Пример

Для работы с System.Transactions необходима ссылка на файл System.Transactions.dll.

Следующая функция показывает, как создать повышаемую транзакцию для двух разных экземпляров SQL Server, представленных двумя разными объектами SqlConnection , которые заключены в оболочку с помощью блок TransactionScope .

В приведенном ниже коде создается блок TransactionScope с инструкцией using и открывается первое соединение, которое автоматически прикрепляет транзакцию к блоку TransactionScope.

Изначально транзакция прикрепляется как упрощенная, а не полностью распределенная транзакция. Второе соединение прикрепляется к транзакции TransactionScope , только если при выполнении команды в первом соединении не возникает исключения. При открытии второго соединения транзакция автоматически повышается до полностью распределенной.

Позднее вызывается метод Complete, который фиксирует транзакцию только в том случае, если не были созданы исключения. Если в любой точке блока TransactionScope возникло исключение, метод Complete вызван не будет и при удалении транзакции TransactionScope в конце ее блока using будет произведен откат распределенной транзакции.

using System;
using System.Transactions;
using Microsoft.Data.SqlClient;

class Program
{
    static void Main(string[] args)
    {
        string connectionString = "Data Source = localhost; Integrated Security = true; Initial Catalog = AdventureWorks";

        string commandText1 = "INSERT INTO Production.ScrapReason(Name) VALUES('Wrong size')";
        string commandText2 = "INSERT INTO Production.ScrapReason(Name) VALUES('Wrong color')";

        int result = CreateTransactionScope(connectionString, connectionString, commandText1, commandText2);

        Console.WriteLine("result = " + result);
    }

    static public int CreateTransactionScope(string connectString1, string connectString2,
                                            string commandText1, string commandText2)
    {
        // Initialize the return value to zero and create a StringWriter to display results.  
        int returnValue = 0;
        System.IO.StringWriter writer = new System.IO.StringWriter();

        // Create the TransactionScope in which to execute the commands, guaranteeing  
        // that both commands will commit or roll back as a single unit of work.  
        using (TransactionScope scope = new TransactionScope())
        {
            using (SqlConnection connection1 = new SqlConnection(connectString1))
            {
                try
                {
                    // Opening the connection automatically enlists it in the
                    // TransactionScope as a lightweight transaction.  
                    connection1.Open();

                    // Create the SqlCommand object and execute the first command.  
                    SqlCommand command1 = new SqlCommand(commandText1, connection1);
                    returnValue = command1.ExecuteNonQuery();
                    writer.WriteLine("Rows to be affected by command1: {0}", returnValue);

                    // if you get here, this means that command1 succeeded. By nesting  
                    // the using block for connection2 inside that of connection1, you  
                    // conserve server and network resources by opening connection2
                    // only when there is a chance that the transaction can commit.
                    using (SqlConnection connection2 = new SqlConnection(connectString2))
                        try
                        {
                            // The transaction is promoted to a full distributed  
                            // transaction when connection2 is opened.  
                            connection2.Open();

                            // Execute the second command in the second database.  
                            returnValue = 0;
                            SqlCommand command2 = new SqlCommand(commandText2, connection2);
                            returnValue = command2.ExecuteNonQuery();
                            writer.WriteLine("Rows to be affected by command2: {0}", returnValue);
                        }
                        catch (Exception ex)
                        {
                            // Display information that command2 failed.  
                            writer.WriteLine("returnValue for command2: {0}", returnValue);
                            writer.WriteLine("Exception Message2: {0}", ex.Message);
                        }
                }
                catch (Exception ex)
                {
                    // Display information that command1 failed.  
                    writer.WriteLine("returnValue for command1: {0}", returnValue);
                    writer.WriteLine("Exception Message1: {0}", ex.Message);
                }
            }

            // If an exception has been thrown, Complete will not
            // be called and the transaction is rolled back.  
            scope.Complete();
        }

        // The returnValue is greater than 0 if the transaction committed.  
        if (returnValue > 0)
        {
            writer.WriteLine("Transaction was committed.");
        }
        else
        {
            // You could write additional business logic here, notify the caller by  
            // throwing a TransactionAbortedException, or log the failure.  
            writer.WriteLine("Transaction rolled back.");
        }

        // Display messages.  
        Console.WriteLine(writer.ToString());

        return returnValue;
    }
}

См. также