It’s always good to analyze code performed by others, please do not consider this a criticism, it is not my intention. Doing that, you will compare technics, learn new things, noted and we become aware of how some transformation rules are made within the maps… and sometimes, most often when things are done in the wrong way, or not quite correct, we gain inspiration or idea to talk about it. And this is one of them, that in fact, I have seen often happening: Applying conditions (if-then-else) using sometimes complex Functoid chain.
So far nothing new, this is a trivial operation that we normally, or often, do. The thing is that often we do it wrong or not quite well.
I have already addressed the processing model of BizTalk maps on my BizTalk Mapping Patterns and Best Practices book but it is always useful to refer again. BizTalk maps follow the model below:
- The BizTalk mapping engine traverses the destination schema from beginning to end;
- The mapping rules are constructed and executed as links are encountered in the destination schema;
- The information is extracted from the source when a link is encountered in the destination schema.
But you need to be extremely aware, and this is important, that when BizTalk mapper engine reaches to a condition rule to construct, all the operation that is present downstream of the condition needs to be translated (executed) before the condition rule itself.
Let’s look at the following example:
Where the basic rule that we want to implement is:
- If the operation is equal to “insert”
- Then we need to set, in the Cross Referencing tables, and return an identifier (Set Common ID Functoid), based on some content from the source schema and in some values present or not in the Cross Referencing tables (Get Common ID Functoid), and mapped to the “CommonId” element in the destination schema
- If the operation is different from “insert”
- Then we need to retrieve an identifier from the Cross Referencing tables (Get Common ID Functoid), again based on some content from the source schema, and mapped the result to the “CommonId” element in the destination schema
So, to be simple and clear, what the BizTalk mapper engine is doing in the picture above is:
- It found 2 rules to translate because we have two links connected with the “CommonId” element
- In the first rule it will: If the operation is equal to “insert”
- Get one element from the “keys” record in the source schema and put it into a variable
- Try’s to retrieve two identifiers’, based on some content from the source schema or static data.
- From the identifiers’ retrieve earlier, it applies some transformation rule (not important for this demo)
- Set and return an identifier, based on the content of:
- one element from the “keys”
- and the transformation rule, associated with the identifiers’ retrieve earlier
- And finally, will check if the operation is equal to “insert”
- Then map the value of the identifier created in the previous step to the element “CommonId”
- In the second rule it will: If the operation is different from “insert”
- Try’s to retrieve two identifiers’, based on some content from the source schema or static data.
- From the identifiers’ retrieve earlier, it applies some transformation rule (not important for this demo)
- And finally, will check if the operation is different to “insert”
- Then map the value of the identifier created in the previous step to the element “CommonId”
Are you already seeing the problem with this sample?
Let’s analyze the generated XSLT code and try to see if it makes more sense and detect the problem in a clear way:
<xsl:variable name="var:v14" select="userCSharp:LogicalNe("insert" , string(@operation))" /> <xsl:variable name="var:v23" select="userCSharp:LogicalEq("insert" , $var:v22)" /> <xsl:variable name="var:v18" select="ScriptNS1:GetCommonID("INPUT1" , "INPUT2" , string($var:v17))" /> <xsl:variable name="var:v19" select="ScriptNS1:GetCommonID("INPUT1" , "INPUT3" , string(keys/myelement/text()))" /> <xsl:variable name="var:v15" select="ScriptNS0:GetValue()" /> <xsl:variable name="var:v20" select="ScriptNS2:GetValue(string($var:v15) , string($var:v18) , string($var:v19))" /> <xsl:if test="string($var:v14)='true'"> <xsl:variable name="var:v21" select="string($var:v20)" /> <btsCommonId> <xsl:value-of select="$var:v21" /> </btsCommonId> </xsl:if> <xsl:variable name="var:v25" select="ScriptNS1:SetCommonID("FamilyMemberVehicle" , "CRM" , $var:v24 , string($var:v20))" /> <xsl:if test="string($var:v23)='true'"> <xsl:variable name="var:v26" select="string($var:v25)" /> <btsCommonId> <xsl:value-of select="$var:v26" /> </btsCommonId> </xsl:if>
And now, do you see the problem with this sample?
The problem is that independent of the type of operation, it is an insert or other operation, it will always set an identifier in the Cross Referencing tables! (which by the way in my scenario, will induce problems – violate key). And this happens because the condition was defined at the end of the rule (in the right corner)
How you should read the rules
Important Note: basically, there are some exceptions, you always need to read the rules:
- from top to bottom in the order that they happier in the destination schema
- and from the left to the right (from the source schema to the destination schema) for a specific link connected to an element, field or record in the destination schema;
So, in this case, the solution here is very simple, we need to move the condition to a position further downstream, which will allow being executed sooner, especially the condition is equal to “insert”, because the second part (get two identifiers’ from the Cross Referencing tables) is common for both rules (then and else).
In this case, we need to place the condition before we execute the set and return an identifier from the Cross Referencing tables operation, as the picture below shows:
If we check the XSLT code once again, now we will notice that:
- We are doing all the common operations before the condition rule to be executed
- And for each particular scenario, we will map the result (not equal to “insert”) or apply more operations and map the result (equal to “insert”)
<xsl:variable name="var:v14" select="userCSharp:LogicalNe("insert" , string(@operation))" /> <xsl:variable name="var:v18" select="ScriptNS1:GetCommonID("INPUT1" , "INPUT2" , string($var:v17))" /> <xsl:variable name="var:v19" select="ScriptNS1:GetCommonID("INPUT1" , "INPUT3" , string(keys/myelement/text()))" /> <xsl:variable name="var:v15" select="ScriptNS0:GetValue()" /> <xsl:variable name="var:v20" select="ScriptNS2:GetValue(string($var:v15) , string($var:v18) , string($var:v19))" /> <xsl:variable name="var:v23" select="userCSharp:LogicalEq("insert" , $var:v22)" /> <xsl:if test="string($var:v14)='true'"> <xsl:variable name="var:v21" select="string($var:v20)" /> <btsCommonId> <xsl:value-of select="$var:v21" /> </btsCommonId> </xsl:if> <xsl:if test="string($var:v23)='true'"> <xsl:variable name="var:v24" select="string($var:v20)" /> <xsl:variable name="var:v25" select="string(keys/siva_viaturaagregadoid/text())" /> <xsl:variable name="var:v26" select="ScriptNS1:SetCommonID("FamilyMemberVehicle" , "CRM" , $var:v25 , string($var:v24))" /> <btsCommonId> <xsl:value-of select="$var:v26" /> </btsCommonId> </xsl:if>
Like this scenario, there are several, more or less complex, more or less critical. Some of them we don’t “even notice” because everything works well but if we check carefully, sometimes will make several unnecessary operations that can induce, or may induce, performance problems in our transformations.
Good practices
As a reference and good practice, when you are implementing conditions using Functoids chains, you should make the conditions as early as possible, i.e., put the conditions (if-then-else) as possible as you can on the left side of the Functoid chain.
This will allow you:
- To group and execute certain task only in the context of the condition (inside the <xsl:if> statement)
- And better performance because we are reducing the “noise” produced in the XSLT code generated by the compiler.
Easy, isn’t it?
Hope you enjoy it.