Недавно мне прислали вопрос о том, как обновить две таблицы в БД Oracle в одной транзакции, отправив два соответствующих сообщения из BizTalk через WCF Oracle адаптер.
Казалось бы, интутитивно, заключение двух Send форм в Atomic transaction scope должно привести к желаемому результату. Однако, такие транзакции выполняются на уровне MessageBox.
Как всегда, решение имеет несколько вариантов. Стандартные варианты в таких случаях - иметь хранимую процедуру, выполняющую обновление двух таблиц и вызывать ее через адаптер, либо воспользовать Expression формой, где через средства .NET выполнить обновление в транзакции.
С новым же WCF Oracle адаптером, у вас появилась возможность поддержки транзакций для серии отдельных сообщений. По этой ссылке можно прочитать про эту возможность. Далее я опишу, как это сделать своими словами.
Итак, у вас есть набор сообщений для Oracle, которые вы хотите отправить в единой транзакции. Первое, что нужно сделать это установить у таких сообщений следующие свойства:
http://schemas.microsoft.com/BizTalk/2003/system-properties#SPID
Это стандартное свойство, которое устанавливается адаптером в идентификатор исходящего порта (в нашем случае Oracle порта). Все сообщения в транзакции должны иметь единое значение этого свойства. Т.е. все такие сообщения должны отправляться через единый исходящий порт (возможно с различными операциями на каждое действие).
http://schemas.microsoft.com/BizTalk/2003/system-properties#InterchangeID
InterchangeID так же стандартное свойство и оно позволяет отнести сообщения с единым значением InterchangeID к единой сессии передачи сообщений (например, при дебатчинге входящего сообщения, все получившиеся сообщения имеют одинаковое значение InterchangeID). Итак, вам придется установить это свойство в оркестровке для серии транзакционных сообщений в единое значение - это может быть просто сгенерированный Guid или взятый InterchangeID из входящего сообщения.
http://Microsoft.LobServices.OracleDB/2007/03/OracleDBSendContextProperties#TransactionState
Третье свойство определено в сборке Microsoft.Adapters.OracleDB.BiztalkPropertySchema.dll, которая идет в поставке с BizTalk Adapter Pack (находится в папке bin, где установлен пакет). Добавьте эту сборку, как ресурс через административную консоль BizTalk (например, в стандартное приложение BizTalk Application 1). В ваш проект же добавьте на нее ссылку (через Add Reference).
TransactionState - это очень важное свойство, т.к. оно регулирует действия над транзакцией, и может принимать следующие значения:
- BEGIN - сообщение, которое может быть либо данными или контрольным (об этом ниже) сообщением, иницирует транзакцию.
- REUSE - сообщение включается в транзакцию.
- COMMIT - контрольное сообщение подтверждает транзакцию.
- ABORT - контрольное сообщение откатывает транзакцию.
Контрольное сообщение имеет следующий вид: <Oracle_Transaction xmlns="http://Microsoft.LobServices.OracleDB/2007/03/Oracle_Transaction"></Oracle_Transaction>. Операция отправляющая контрольное сообщение в адаптер должна мэпиться на SOAP action "http://Microsoft.LobServices.OracleDB/2007/03/Oracle_Transaction".
Схема контрольного сообщения (дабы не создавать его руками) находится в папке Schemas, где установлен BizTalk Adapter Pack.
Это все о свойствах.
Сам WCF-custom адаптер с байндингом oracleDBBinding должен иметь свойство enableBizTalkCompatibilityMode установленным в true (по умолчанию, это так).
Небольшой пример. Я получаю входящее сообщение, создаю два исходящих, каждое из которых обновляет различные таблицы Oracle и затем завершаю транзакцию.
Моя оркестровка выглядит так:
В форме MessageAssignment происходит самое интересное: я устанавливаю свойства всей серии Oracle сообщений:
doc = new System.Xml.XmlDocument();
doc.LoadXml("<ns0:Oracle_Transaction xmlns:ns0='http://Microsoft.LobServices.OracleDB/2007/03/Oracle_Transaction'></ns0:Oracle_Transaction>");
mCommit = doc;
mInsertProduct(Microsoft.Adapters.OracleDB.BiztalkPropertySchema.TransactionState) = "BEGIN";
mInsertProduct(BTS.InterchangeID) = mInbound(BTS.InterchangeID);
mInsertPO(Microsoft.Adapters.OracleDB.BiztalkPropertySchema.TransactionState) = "REUSE";
mInsertPO(BTS.InterchangeID) = mInbound(BTS.InterchangeID);
mCommit(Microsoft.Adapters.OracleDB.BiztalkPropertySchema.TransactionState) = "COMMIT";
mCommit(BTS.InterchangeID) = mInbound(BTS.InterchangeID);
Последовательность отправки сообщений: mInsertProduct, mInsertPO, mCommit.
Порт Oracle в консоли администратора имеет следующие настройки WCF-Custom для SOAP Action Header:
<BtsActionMapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Operation Name="InsertProduct" Action="http://Microsoft.LobServices.OracleDB/2007/03/SUPPLIER/Table/PRODUCT/Insert" />
<Operation Name="InsertPO" Action="http://Microsoft.LobServices.OracleDB/2007/03/SUPPLIER/Table/PURCHASEORDER/Insert" />
<Operation Name="ControlTransaction" Action="http://Microsoft.LobServices.OracleDB/2007/03/Oracle_Transaction" />
</BtsActionMapping>
Вот пожалуй и все. Теперь вставка в две разные таблицы будет производится в единой транзакции.
Стас