TransactionScope: предотвращение распределенных транзакций - PullRequest
18 голосов
/ 06 июля 2010

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

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

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

Пока все хорошо ..?

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

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

С практической точки зрения, я ...

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

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;
                        }
                    }
                }
            }

Ответы [ 3 ]

24 голосов
/ 06 июля 2010

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

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

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

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

2 голосов
/ 06 июля 2010

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

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

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

1 голос
/ 06 июля 2010

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

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

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

...