While working on a BTS 2006 solution – I decided to use the SQL Adapter to call a
stored Proc to update data.

While the SQL Adapter wizard is OK, there’s no real reason to use it. I
usually delete everything it creates, apart from the Schema for namespace samples.
The Namespaces you specify through the wizard is there mainly for the SQL Adapter
to figure out where the bits are for it to process, and where return results should
be inserted into……

In alot of solutions I build, I usually have a single generic SQL Update Orchestration,
not an Orch, Schema + Port for each type of SQL action required.

The trick to all this is how the SQL Adapter handles the messages sent to
it.
More details is found in the SQLXML documentation.

The paper back version:

Let’s say I have two tables and a Stored Proc that I want to use within the SAME DB
(if I want to talk to different DB’s then we’d need to create a separate message for
the different DB’s to update due to the fact that the physical SQL Port (whether it
be ‘dynamic’ or physical) e.g. SQL://ServerName/DB…….

Table A: PacMan Players
Fields –
Name, email

Table B: PacMan Scores

Fields – email, score

Stored Proc: UpdateScores
Params:
email, score, gametime

If these three were in the same DB here’s the message(s) that you’d need to send to
the SQL Adapter (could even be via CBR and not ALWAYS an Orch).

e.g. a sample message for stored procs.
<sqlRequest xmlns=’http://micksdemos.sql’>
     <Updates>
            <UpdateScores email=’[email protected] score=’54’
gametime=’1200′ />
           
<AnotherStoredProc p1=’2′ p2=’aaa’ p3=’….’ />
      </Updates>
      <Results>
                    
<!– **** Set to be ANY element here, with ‘skip’ processing set via the schema
**** –>
      </Results>
</sqlResults>

e.g. a sample message for tables (further details on this message
structure can be obtained from SQLXML Documents)
<sqlRequest xmlns=’http://micksdemos.sql’>   

      <sync>
             <after>
                 
<PacManScores email=’[email protected] score=’22000′
/>
                 
<PacManPlayers Name=’mick’ email=’[email protected]  />
                 

            
</after>
      </sync>
</sqlResults>

Now the interesting thing upon the results being returned for the called Stored
Procs
We sent down batches of 400 updates to be performed via the stored proc method,
and the results were supprising!!!!

We got a message back via one of the several Two-Way SQL Ports defined (each
talking to a different database, being activated via CBR)

The return results was a Multi-part message with 400 parts!!!!!! In
this case I was waiting for the return message within an Orchestration and then carrying
on (mainly for BAM purposes to capture timings, average call times etc)

Do you know how hard it was to find an appropriate message type?????? If I made a
multi-part message type with 5 parts it’s not 400. If I made one with 400 parts (each
part was a type of ANY) then I’m sure we’d have a batch in the future with 401 updates…boom!
blows up.

So my challenge was to find the appropriate message type for this return message…..needless
to say “I’m still looking”
I tried

(1) XLANGMessage – not serializable and bts wont compile in the IDE. This is the most
logical cause then I could just go through the parts grabbing each result message.
(2) XLANGPart – long shot, individual part of a message, but also if a Message if
declared as ANY type then this is the .NET Message Type that represents it behind
the scenes.
(3) ANY – Compiled and run, error when the results message is returned, as the ANY
type is still dealing with a single part message
(4) XMLDocument – yeah right! Sort of the one that you cover your eyes, run the test
and peep through your fingers looking at the screen to see if it worked….or more
like *hoped’ it worked 🙂

Solution: Create a simple Custom Pipeline Component to Consolidate the Return
parts


The Orchestration is fine to go on continuing processing.
The thing that stumped me is that I send in a Batch within a Single XML Document,
why dont I get that as a response??


I could imagine when sending a single update this problem never occurs.
(and it hasnt in the past)

Here’s the custom pipeline component – this one’s in VB.NET as per the client’s
coding standards on this.
(I use the VirtualStream found in the SDK)
– this is not production ready
code. Further stress testing needed.

Here’s a snippet showing the execute method (BTSHelper.VirtualStream – is the VirtualStream
class from the BTS 2006 SDK)

#Region “IComponent Members”
Public Function Execute(ByVal pContext As IPipelineContext, ByVal pInMsg As IBaseMessage)
As _
IBaseMessage Implements IComponent.Execute

Try
                  
Dim msgReturn As IBaseMessage = InternalMyExecute(pContext, pInMsg)
                  
Return (msgReturn)
Catch ex As Exception
                  
Throw ex
End Try         

End Function

Private Function InternalMyExecute(ByVal pc As IPipelineContext, ByVal inMsg
As IBaseMessage) As IBaseMessage
         Dim outMsg As IBaseMessage
= Nothing
         Dim outPt As IBaseMessagePart = Nothing
         Dim outStream As BTSHelper.VirtualStream
= Nothing
         Dim sw As StreamWriter = Nothing
         Try
              
If (inMsg.PartCount > 1) Then ‘combine all the parts into one – painful return
results from SQL.
                         
outMsg = pc.GetMessageFactory().CreateMessage()
                         
outMsg.Context = inMsg.Context
                         
outPt = pc.GetMessageFactory().CreateMessagePart()
                         
outStream = New BTSHelper.VirtualStream()
                         
sw = New StreamWriter(outStream)
                         
sw.Write(“<{0}>”, _documentRootElement)
                         
For i As Integer = 0 To inMsg.PartCount – 1
                                       
Dim sptName As String = String.Empty
                                       
Dim s As String = GetMessagePartAsString(inMsg.GetPartByIndex(i, sptName))
                                       
sw.Write(s)
                          Next
                         
sw.Write(“</{0}>”, _documentRootElement)
                         
sw.Flush()
                         
‘ we DONT want to close the stream i.e. sw.close()
                         
outStream.Seek(0, SeekOrigin.Begin)
                         
outPt.Data = outStream
                          outMsg.AddPart(“Body”,
outPt, True)
                          Return
(outMsg)
                 
Else ‘single part
                        
Return (inMsg)
                 
End If
          Catch ex As Exception
                  inMsg.SetErrorInfo(ex)
‘ the inMessage is the one that gets reported on in BizTalk within the pipeline
                  EventLog.WriteEntry(_EVENTLOG_SOURCE,
“SQL Combiner Exception Internal Execute- ”          

                         
+ ControlChars.CrLf + ControlChars.CrLf + ex.Message, EventLogEntryType.Error)
                 
Throw ex
          Finally

          End Try
End Function

Private Function GetMessagePartAsString(ByVal pt As IBaseMessagePart) As String
                
Dim xdoc As XmlDocument = Nothing
           Try
                      
xdoc.Load(pt.GetOriginalDataStream())
                      
Return (xdoc.DocumentElement.OuterXml)
           Catch ex As Exception
                      
Throw ex
           Finally
                      
xdoc = Nothing
                      
GC.Collect()
           End Try
End Function

Public Sub CopyStream(ByVal src As Stream, ByVal dst As Stream)
            Try
                 
If (src.CanSeek) Then
                           
src.Seek(0, SeekOrigin.Begin)
                 
End If
                 
Dim DATA_BLOCK As Integer = 4096
                 
Dim bytesRead As Integer = 0
                 
Dim buff(DATA_BLOCK – 1) As Byte

                 
bytesRead = src.Read(buff, 0, DATA_BLOCK)
                 
While (bytesRead > 0)
                          
dst.Write(buff, 0, bytesRead)
                          
bytesRead = src.Read(buff, 0, DATA_BLOCK)
                 
End While
            Catch ex As Exception
                 
Throw ex
            End Try
End Sub

Private Sub CopyMessageParts(ByVal sourceMessage As IBaseMessage, ByVal destinationMessage
As IBaseMessage, ByVal newBodyPart As IBaseMessagePart)

                
Dim bodyPartName As String = sourceMessage.BodyPartName

                
For i As Integer = 0 To sourceMessage.PartCount – 1
                             
Dim partName As String = Nothing
                             
Dim messagePart As IBaseMessagePart = sourceMessage.GetPartByIndex(i, partName)
                             
If (partName <> bodyPartName) Then
                                           
destinationMessage.AddPart(partName, messagePart, False)
                              Else
                                            destinationMessage.AddPart(bodyPartName,
newBodyPart, True)
                             
End If
                
Next
End Sub

#End Region

Grab the code from below – This sample is aimed to be something to look and discover
from rather than be a ‘ready made installable package’

SqlCombiner.vb
(9.63 KB)