// This function is running inside the TransactionScope and the SqlConnection should pick up the current transaction // This will attempt to execute a stored procedure with a single parameter called @Data of type ntext private void SendMessage(IBaseMessage message, TransactionalTransmitProperties properties) { TextReader reader = new StreamReader(message.BodyPart.Data); string msgText = reader.ReadToEnd(); string storedProcedureName = properties.CmdText; string connectionString = properties.ConnectionString; using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); SqlCommand command = new SqlCommand(storedProcedureName, connection); command.CommandType = CommandType.StoredProcedure; command.Parameters.Add(new SqlParameter("@Data", msgText)); command.ExecuteScalar(); } }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // thread per endpoint batch private void BatchWorker(string outboundTransportLocation, ArrayList messages) { // we did an enter for every message - so we should ensure we have a correspending leave int leaveCount = messages.Count; try { // all the messages in this batch should have the same properties - so just take the first one IBaseMessage firstMessage = (IBaseMessage)messages[0]; SystemMessageContext firstMessageContext = new SystemMessageContext(firstMessage.Context); TransactionalTransmitProperties properties = new TransactionalTransmitProperties(firstMessageContext.OutboundTransportLocation); string config = (string)firstMessage.Context.Read("AdapterConfig", this.propertyNamespace); if (config != null) { // There is a configuration DOM so we are doing a Static Send XmlDocument locationConfigDom = new XmlDocument(); locationConfigDom.LoadXml(config); properties.LocationConfiguration(locationConfigDom); } else { // Add dynamic send here } foreach (IBaseMessage message in messages) { CommittableTransaction transaction = null; try { // create the System.Transactions transaction - this is not yet a DTC transaction transaction = new CommittableTransaction(); // give the CommittableTransaction to the batch and it will take the responsibility to Commit it. // the TransactionInterop.GetDtcTransaction call inside the base TxnBase class actually causes the // DTC transaction to be created - this can be observed in the COM+ Explorer while debugging this code using (Batch batch = new TransactionalDeleteBatch(this.transportProxy, this.control, transaction)) { // note the options EnterpriseServicesInteropOption.Full in the future when resource managers // understand light weight transactions this might not be necessary but we need it for now using (TransactionScope ts = new TransactionScope(transaction, TimeSpan.FromHours(1), EnterpriseServicesInteropOption.Full)) { SendMessage(message, properties); // an exception will skip this next line ts.Complete(); } // IMPORTANT: a Delete is part of the same transaction as the send operation batch.DeleteMessage(message); // IMPORTANT: if there was a response to submit it would be added here // - in the same batch and same transaction as the Delete batch.Done(); } } catch (Exception e) { // in this scenario we will explicitly Rollback the transaction on failure if (transaction != null) { transaction.Rollback(); } // Remember to set the exception on the message itself - this will now appear in tracking in addition to the EventLog message.SetErrorInfo(e); // Any failures need to be retried - but the change of state back on BizTalk is outside of the transaction // that has been used to attempt to send the message - after all that transaction will undoubtedly get rollback with the failure. // We say this batch is "non-transactional" from the adapter's point of view - though internally in BizTalk its still a transaction. using (TransmitResponseBatch batch = new TransmitResponseBatch(this.transportProxy, new TransmitResponseBatch.AllWorkDoneDelegate(AllWorkDone))) { batch.Resubmit(message, false, null); batch.Done(); } } // an exception will skip this line - an exception means the Done hasn't been called or was successful so we have a leave to do leaveCount--; } } catch (Exception e) { this.transportProxy.SetErrorInfo(e); } finally { // perform any remain leaves - hopefully none - if everything was successful then leaveCount will be 0 for (int i = 0; i < leaveCount; i++) { this.control.Leave(); } } }