ADN Open CIS
Сообщество программистов Autodesk в СНГ

22/02/2015

Пример использования групп транзакций

Вопрос: Я создаю приложение, в котором мне нужно:

  1. Загрузить семейство из файла
  2. Получить объект FamilyManager из редактора семейств и получить доступ к некоторым параметрам.
  3. Поместить экземпляр семейства в проект.

Загрузка семейства в проект, осуществляется в транзакции.  Затем, чтобы открыть редактор семейств, транзакция должна быть закрыта. А для размещения экземпляра изменённого семейства в проект, транзакция снова должна быть открыта.

По этой причине Revit создает два состояния, которые можно отменить с помощью операция «Отмена» - одно для каждой транзакции.

Если ли способ объединить две транзакции в одну, таким образом, чтобы была лишь одна операции отмены?

Я использую TransactionMode.Manual.

Ответ: Я рад, что вы задали такой вопрос. Конечно это возможно. Группы транзакций как раз для этого и существуют. Алгоритм прост:

  1. Запустите новую группу транзакций. Не забудьте дать ей имя.
  2. Выполните вашу первую транзакцию.
  3. Откройте семейство в редакторе
  4. Загрузите семейство в проект с помощью второй транзакции.
  5. Закройте группу транзакций с помощью метода Assimilate.

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

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

Надеюсь эта информация окажется полезной. Для более подробной информации обратитесь к файлу справки RevitAPI.chm. Смотрите класс TransactionGroup.

Ответ: Отлично. Я хотел бы убедиться, что я правильно обрабатываю исключения.

Когда группа транзакций откатывается, все подтвержденные и неподтверждённые транзакции внутри группы также откатываются?

Когда группа транзакций подтверждена, все транзакции внутри группы, должны быть подтверждены?

Код - C#: [Выделить]
  1.   using( TransactionGroup transGroup = new TransactionGroup( document ) )
  2.   {
  3.     transGroup.Start( "Группа транзакций" );
  4.  
  5.     using( Transaction firstTrans = new Transaction( document ) )
  6.     {
  7.       try
  8.       {
  9.         firstTrans.Start( "Первая транзакция" );
  10.  
  11.         // что-нибудь делаем тут
  12.  
  13.         firstTrans.Commit();
  14.       }
  15.       catch
  16.       {
  17.         transGroup.Rollback(); // <-- Нужно ли откатывать первую транзакцию??
  18.  
  19.         return Result.Failed;
  20.       }
  21.     }
  22.  
  23.     using( Transaction secondTrans = new Transaction( document ) )
  24.     {
  25.       try
  26.       {
  27.         secondTrans.Start( "Вторая транзакция" );
  28.  
  29.         // что-нибудь делаем тут
  30.  
  31.         secondTrans.Commit();
  32.       }
  33.       catch
  34.       {
  35.         transGroup.Rollback(); // <-- Нужно ли откатывать вторую транзакцию??
  36.  
  37.         return Result.Failed;
  38.       }
  39.     }
  40.  
  41.     transGroup.Assimilate();
  42.  
  43.     return Result.Succeeded;
  44.   }

Ответ: Да и на самом деле нет. Ваше утверждение почти верное, за исключением первой части. Я выделю это здесь: «… все подтвержденные и неподтвержденные транзакции…». Суть в том, что вы не можете закрыть (т.е. подтвердить или откатить) группу транзакций, пока есть открытые транзакции. Все транзакции, которые были начаты внутри группы, должны быть корректно завершены или отменены, перед закрытием группы транзакций.

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

Что касается вашего примера кода, то в целом все верно, за исключением пары моментов. Один из них более важен, чем другой. С него и начнем.

 Не нужно откатывать открытую транзакцию в блоке catch, так как откат будет выполнен автоматически, после выхода из блока using. Однако, в вашем случае, откат в блоке catch может быть даже довольно опасным. Проблема в том, что в действительности (в теории) вы можете получить исключение и при запуске транзакции при выполнении метода Start. И в этом случае, вы получите еще одно исключение при попытке отката не начавшейся транзакции. А исключение в блоке обработке исключения – не очень хорошая ситуация. Поэтому, если вы хотите вызвать метод RollBack, то нужно проверять, что транзакция действительно начата. (Для этого есть соответствующие методы)

Другая проблема менее опасная, но может привести к неожиданным последствиям. Программист должен предчувствовать, что подтверждение транзакции не обязательно завершится успешно. Для этого есть несколько причин. Одна из причин – неудачная регенерация модели. Если подтверждение транзакции прошло неудачно, то не нужно продолжать дальше операцию со второй транзакцией. Игнорирование этой проверки привести к не ошиканным результатам.

И последнее. Это не ошибка, но может сделать ваш код более простым. Не нужно каждый раз создавать новую транзакцию. Можно просто перезапускать уже существующую, задав ей новое имя в методе Start().

Исходя из моих комментариев, получится вот такой код, делающий то же самое:

Код - C#: [Выделить]
  1.   using( TransactionGroup transGroup
  2.     = new TransactionGroup( document ) )
  3.   {
  4.     using( Transaction trans
  5.       = new Transaction( document ) )
  6.     {
  7.       try
  8.       {
  9.         transGroup.Start( "Какое-то действие" );
  10.  
  11.         trans.Start( "Первая транзакция" );
  12.  
  13.         // Что-то делаем
  14.  
  15.         if( trans.Commit() != TransactionStatus.Committed )
  16.         {
  17.           return Result.Failed;
  18.         }
  19.  
  20.         trans.Start( "Вторая транзакция" );
  21.  
  22.         // Что-то еще делаем
  23.  
  24.         trans.Commit();
  25.  
  26.         if( trans.Commit() != TransactionStatus.Committed )
  27.         {
  28.           return Result.Failed;
  29.         }
  30.  
  31.         transGroup.Assimilate();
  32.       }
  33.       catch
  34.       {
  35.         return Result.Failed;
  36.       }
  37.     }
  38.     return Result.Succeeded;
  39.   }

Ответ: Еще один вопрос. В соответствие с логикой по проверке подтверждения транзакции, нужно ли также учитывать и проверять, что метод Transaction.Start возвратил TransacrtionStatus.Started? Или есть причины, по которым это делать не нужно?

Ответ: Я уже несколько раз обсуждал это в прошлом. Не смотря на то, что действительно, в теории метод Start() может вернуть какой-либо другой статус, это очень маловероятно, что это произойдет в общедоступной версии API. (Примечание: Мы, программисты Revit, во внутреннем коде используем те же самые методы, и для нас более вероятно, что произойдет такая ситуация). В публичном API, если что-то не так при старте транзакции, то возникнет исключение.

Источник: http://thebuildingcoder.typepad.com/blog/2015/02/using-transaction-groups.html

Обсуждение: http://adn-cis.org/forum/index.php?topic=1887

Опубликовано 22.02.2015