TransactionScope: предотвращение распределенных транзакций

У меня есть родительский объект (часть DAL), который содержит, среди прочего, коллекцию (List<t>) дочерних объектов.

Когда я возвращаю объект обратно в БД, я ввожу/обновляю родительский элемент, а затем прохожу через каждый дочерний элемент. Для удобства обслуживания я поместил весь код для дочернего элемента в отдельный частный метод.

Я собирался использовать стандартные транзакции ADO, но в своих путешествиях я наткнулся на объект TransactionScope, который, я считаю, позволит мне обернуть все взаимодействие с БД в родительском методе (вместе со всем взаимодействием в дочернем методе) в одна транзакция.

До сих пор так хорошо..?

Итак, следующий вопрос: как создавать и использовать соединения в этом TransactionScope. Я слышал, что использование нескольких подключений, даже если они относятся к одной и той же БД, может заставить TransactionScope думать, что это распределенная транзакция (с участием некоторой дорогостоящей работы DTC).

Это случай? Или это, как я, кажется, читаю в другом месте, случай, когда использование одной и той же строки соединения (которая будет поддаваться пулу соединений) будет прекрасной?

На самом деле, я...

  • Создайте отдельные подключения в родительском и дочернем (хотя и с той же строкой соединения)
  • Создайте соединение в родителе и передайте его как параметр (кажется мне неуклюжим)
  • Сделайте что-то еще...?

UPDATE:

Пока он появляется, я был бы в порядке, используя мои обычные .NET3.5 + и SQL Server 2008+, другая часть этого проекта будет использовать Oracle (10g), чтобы я мог также практиковать технику, которая может использоваться последовательно проектов.

Итак, я просто передаю соединение дочерним методам.


Вариант 1 Пример кода:

using (TransactionScope ts = new TransactionScope())
            {
                using (SqlConnection conn = new SqlConnection(connString))
                {
                    using (SqlCommand cmd = new SqlCommand())
                    {
                        cmd.Connection = conn;
                        cmd.Connection.Open();
                        cmd.CommandType = CommandType.StoredProcedure;

                        try
                        {
                            //create & add parameters to command

                            //save parent object to DB
                            cmd.ExecuteNonQuery();

                            if ((int)cmd.Parameters["@Result"].Value != 0)
                            {
                                //not ok
                                //rollback transaction
                                ts.Dispose();
                                return false;
                            }
                            else //enquiry saved OK
                            {
                                if (update)
                                {
                                    enquiryID = (int)cmd.Parameters["@EnquiryID"].Value;
                                }

                                //Save Vehicles (child objects)
                                if (SaveVehiclesToEPE())
                                {
                                    ts.Complete();
                                    return true;
                                }
                                else
                                {
                                    ts.Dispose();
                                    return false;
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            //log error
                            ts.Dispose();
                            throw;
                        }
                    }
                }
            }

Ответы

Ответ 1

Многие поставщики ADO базы данных (такие как Oracle ODP.NET) действительно начинают распределенные транзакции, когда вы используете TransactionScope для транзакции по нескольким соединениям, даже если они используют одну и ту же строку соединения.

Некоторые поставщики (например, SQL2008 в .NET 3.5+) распознают, когда создается новое соединение в области транзакций, относящееся к одной и той же строке подключения, и не приведет к работе DTC. Но любая дисперсия в строке соединения (например, параметры настройки) может исключить это из-за этого - и поведение вернется к использованию распределенной транзакции.

К сожалению, единственное надежное средство обеспечения ваших транзакций будет работать вместе без создания распределенной транзакции - передать объект соединения (или IDbTransaction) методам, которые необходимо "продолжить" в одной транзакции.

Иногда это помогает повысить соединение с членом класса, в котором вы выполняете работу, но это может создать неудобные ситуации - и усложняет управление временем жизни и удалением объекта соединения (поскольку в целом это исключает использование оператор using).

Ответ 2

Эмпирически я определил, что (для поставщика SQL Server), если процесс может использовать преимущества пула соединений для совместного использования соединения (и транзакции) между родительским и дочерним процессами, DTC не обязательно будет задействован.

Это большой "если", однако, согласно вашему примеру, соединение, созданное родительским процессом, не может быть передано дочерними процессами (вы не закрываете/не отключаете соединение до вызова дочерних процессов). Это приведет к транзакции, которая охватывает два реальных соединения, что приведет к продвижению транзакции к распределенной транзакции.

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

Ответ 3

В вашем примере TransactionScope все еще находится в контексте метода, вы можете просто создать SqlTransaction с несколькими командами под ним. Используйте TransactionScope, если вы хотите переместить транзакцию из метода, например, вызывающего объекта этого метода или если вы обращаетесь к нескольким базам данных.

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

Обновить 2:, передайте IDbTransaction дочернему классу. Соединение можно извлечь из этого.