BizTalk Mapper Patterns: How to Map Name Value Pair to a Hierarchical Schema

Posted: November 8, 2012 in BizTalk
Tags: , , ,

In my last mapping pattern post, I explained how and described the pros and cons of four methods to accomplish a mapping from hierarchical schema to a name value pair… so the logically next step would be to describe the inverse process.

When I decided to implement this mapping I really thought it would be extremely simple and almost not worth talking about… how I was wrong! It became really complicated, and exciting, as I tried to find a solution to optimize and improve the map.

As a developer the first approach we think is to try to solve this mapping problem using only the available functoids, i.e. without custom XSLT. I quickly discarded this option and you will see why.

Note: Before I start the solution for this problem we need to notice that two element are mapped directly:

  • “Id” to “NProcesso”
  • and “ServiceName” to “ServiceName”
First Solution: Using only functoids (without custom XSLT)

To solve this mapping problem using this approach, for each element in the destination schema we need to drag:

  • One Equal functoid and drag a link from the element “Name” in the source schema to this functoid, this will be the first condition in the functoid
    • And in the second condition we need to put the element name of the destination schema that we try to map, for example “Type”.
  • Drag a Value Mapping (Flattening) functoid to the grid
    • Drag a link from the Equal functoid to this Value Mapping (Flattening) functoid
    • Drag a link from the “Value” element in the source element to the Value Mapping (Flattening) functoid
    • And finally we need to drag a link from the Value Mapping (Flattening) functoid to the respective element in the destination schema, in this case “Type” element as you can see in the picture bellow:

Name-value-to-hierarchical-using-only-functoids

  • We need to repeat the above steps for all the element except the “IPRoute” element, until we get the following map:

Name-value-to-hierarchical-using-only-functoids-2

  • Because “IPRoute” element is a repeating element, we need to take a different approach:
    • We need to drag a Looping functoid and drag a link from “Property” record in the source schema to this functoid and then drag a link from the Looping functoid to the “IPRoute” element in the destination schema
    • Then we need to make the exact same steps described earlier (Equal functoid and Value Mapping (Flattening) functoid)
    • But because we want to create the “IPRoute” element ONLY if the name is equal to “IPRoute”, then we need to drag a link from the Equal functoid to the “IPRoute” element in the destination schema.

Name-value-to-hierarchical-using-only-functoids-3

And this is it, it seems simple and it is!! However, this approach suffers from a serious problem that we can only detect by analyzing the XSLT regenerated by the BizTalk mapping engine: Performance!

If we analyze the XSLT regenerated by the BizTalk mapping engine by:

  • Right-click in the map and select the option “Validate Map”
  • In the Output windows, press CRTL key and click on the link of “The file in the output XSLT is stored in the following file”, it will open this file in a new windows
  • Right-click and select “View Source” option

We will see that for each element in the destination schema it will be one for-each element:

<xsl:for-each select="Properties/Property">
  <xsl:variable name="var:v1" select="userCSharp:LogicalEq(string(Name/text()) , &quot;Type&quot;)" />
  <xsl:if test="string($var:v1)='true'">
    <xsl:variable name="var:v2" select="Value/text()" />
    <Type>
      <xsl:value-of select="$var:v2" />
    </Type>
  </xsl:if>
</xsl:for-each>
<xsl:for-each select="Properties/Property">
  <xsl:variable name="var:v3" select="string(Name/text())" />
  <xsl:variable name="var:v4" select="userCSharp:LogicalEq($var:v3 , &quot;Protocol&quot;)" />
  <xsl:if test="string($var:v4)='true'">
    <xsl:variable name="var:v5" select="Value/text()" />
    <Protocol>
      <xsl:value-of select="$var:v5" />
    </Protocol>
  </xsl:if>
</xsl:for-each>

This means that if we have 50 occurrences of “Property” record, each filled with the elements Name and Value, we will have 50 iterations for each element that we want to map in the destination schema… in this scenario we have 12 elements, this means 600 iterations and will be worse if we are working with large maps or with high amounts of “Property” record occurrence.

Limitations of this approach:

  • Lack of performance
  • If the destination schema has many elements it takes to much work to do this kind of mapping and because we need many links and functoids to do this simple task it may become difficult to read the map.
  • If we add a new element in the destination schema, it requires that we have to rectify the mapping
Second Solution: Dynamic mapping using Inline XSLT

I soon realized that if I wanted a really good and effective solution, I would have to use custom XSLT.

And the second approach that I thought was trying to make a dynamic mapping, similarly to the third solution that I have accomplished in the “BizTalk Mapper Patterns: How to Map Hierarchical Schema to a Name Value Pair” problem

To accomplish this, we need to:

  • Drag Scripting functoid to the map grid
    • In the scripting type select “Inline XSLT” option
    • In the Inline script put the following code:
<xsl:for-each select="/s0:Provisioning/Properties/Property">
  <xsl:if test="Name/text()!='IPRoute'">
    <xsl:element name="{normalize-space(*[local-name()='Name']/text())}">
      <xsl:value-of select="Value/text()"/>
    </xsl:element>
  </xsl:if>
</xsl:for-each> 

Finally drag a link from the Scripting functoid to the “Type” element in the destination schema:

Name-value-to-hierarchical-Dynamic-mapping-using-Inline-XSLT

This looked like be my favorite approach because is complete dynamic. If another element was added to the destination schema I didn’t need to fix the mapping!

But unfortunately this approach has several serious limitations.

Limitations of this approach:

  • The script only work well if all the elements contained in the “Properties” record, are coming filled in the correct order of the elements in the destination schema.
  • Don’t work with nested records (or sub-records), if you notice in the script I ignore all “IPRoute” names

I could probably find other limitations but for me these two are enough to discard this approach.

Third Solution: Using Inline XSLT along with XPath queries

After analyzing all the advantages and disadvantages, for me this is the best (only) approach to accomplish this type of mapping problem. Again, because basically solves all limitations of previous solutions: it’s easy to create (only need basic knowledge of XSLT and XPath) and don’t have performance problems.

To accomplish this, we need to:

  • Replace the code of the Scripting functoid, existing in the previous solution, by:
<Type>
  <xsl:value-of select="//Properties/Property[Name='Type']/Value/text()" />
</Type>
<Protocol>
  <xsl:value-of select="//Properties/Property[Name='Protocol']/Value/text()" />
</Protocol>
<Pool>
  <xsl:value-of select="//Properties/Property[Name='Pool']/Value/text()" />
</Pool>
<VPNName>
  <xsl:value-of select="//Properties/Property[Name='VPNName']/Value/text()" />
</VPNName>
<IPAddress>
  <xsl:value-of select="//Properties/Property[Name='IPAddress']/Value/text()" />
</IPAddress>
<IPNetmask>
  <xsl:value-of select="//Properties/Property[Name='IPNetmask']/Value/text()" />
</IPNetmask>
<LAN>
  <xsl:for-each select="Properties/Property[Name='IPRoute']">
    <IPRoute>
      <xsl:value-of select="./Value/text()" />
    </IPRoute>
  </xsl:for-each>
</LAN>
<VirtualRouter>
  <xsl:value-of select="//Properties/Property[Name='VirtualRouter']/Value/text()" />
</VirtualRouter>
<IdleTimeout>
  <xsl:value-of select="//Properties/Property[Name='IdleTimeout']/Value/text()" />
</IdleTimeout>
<SessionTimeout>
  <xsl:value-of select="//Properties/Property[Name='SessionTimeout']/Value/text()" />
</SessionTimeout>
<TunnelType>
  <xsl:value-of select="//Properties/Property[Name='TunnelType']/Value/text()" />
</TunnelType>

Name-value-to-hierarchical-Dynamic-mapping-using-Inline-XSLT

In this first approach, I tried to keep the code simple, but there is an important limitation in this code … I’m not validate the existence of optional fields. To do that we need to put the code a little more elaborate:

<xsl:choose>
  <xsl:when test="count(//Properties/Property[Name='Type']) > 0">
    <Type>
      <xsl:value-of select="//Properties/Property[Name='Type']/Value/text()" />
    </Type>
  </xsl:when>
</xsl:choose>
<xsl:choose>
  <xsl:when test="count(//Properties/Property[Name='Protocol']) > 0">
    <Protocol>
      <xsl:value-of select="//Properties/Property[Name='Protocol']/Value/text()" />
    </Protocol>
  </xsl:when>
</xsl:choose>
<xsl:choose>
  <xsl:when test="count(//Properties/Property[Name='Pool']) > 0">
    <Pool>
      <xsl:value-of select="//Properties/Property[Name='Pool']/Value/text()" />
    </Pool>
  </xsl:when>
</xsl:choose>
<xsl:choose>
  <xsl:when test="count(//Properties/Property[Name='VPNName']) > 0">
    <VPNName>
      <xsl:value-of select="//Properties/Property[Name='VPNName']/Value/text()" />
    </VPNName>
  </xsl:when>
</xsl:choose>
<xsl:choose>
  <xsl:when test="count(//Properties/Property[Name='IPAddress']) > 0">
    <IPAddress>
      <xsl:value-of select="//Properties/Property[Name='IPAddress']/Value/text()" />
    </IPAddress>
  </xsl:when>
</xsl:choose>
<xsl:choose>
  <xsl:when test="count(//Properties/Property[Name='IPNetmask']) > 0">
    <IPNetmask>
      <xsl:value-of select="//Properties/Property[Name='IPNetmask']/Value/text()" />
    </IPNetmask>
  </xsl:when>
</xsl:choose>
<xsl:choose>
  <xsl:when test="count(//Properties/Property[Name='IPRoute']) > 0">
    <LAN>
      <xsl:for-each select="Properties/Property[Name='IPRoute']">
        <IPRoute>
          <xsl:value-of select="./Value/text()" />
        </IPRoute>
      </xsl:for-each>
    </LAN>
  </xsl:when>
</xsl:choose>
<xsl:choose>
  <xsl:when test="count(//Properties/Property[Name='VirtualRouter']) > 0">
    <VirtualRouter>
      <xsl:value-of select="//Properties/Property[Name='VirtualRouter']/Value/text()" />
    </VirtualRouter>
  </xsl:when>
</xsl:choose>
<xsl:choose>
  <xsl:when test="count(//Properties/Property[Name='IdleTimeout']) > 0">
    <IdleTimeout>
      <xsl:value-of select="//Properties/Property[Name='IdleTimeout']/Value/text()" />
    </IdleTimeout>
  </xsl:when>
</xsl:choose>
<xsl:choose>
  <xsl:when test="count(//Properties/Property[Name='SessionTimeout']) > 0">
    <SessionTimeout>
      <xsl:value-of select="//Properties/Property[Name='SessionTimeout']/Value/text()" />
    </SessionTimeout>
  </xsl:when>
</xsl:choose>
<xsl:choose>
  <xsl:when test="count(//Properties/Property[Name='TunnelType']) > 0">
    <TunnelType>
      <xsl:value-of select="//Properties/Property[Name='TunnelType']/Value/text()" />
    </TunnelType>
  </xsl:when>
</xsl:choose>

Limitations of this approach:

  • Because we use scripting functoids we cannot read the entire map visually. We need to open the functoids and read, mainly, the XSLT code.
  • Need basic knowledge of XSLT and XPath
  • If we add a new element in the destination schema, it requires that we have to rectify the mapping

The sample code is available for download in Code Gallery:.

BizTalk Mapper Patterns: How to Map Name Value Pair to a Hierarchical Schema (59.2 KB)
Microsoft Code Gallery

Comments
  1. […] BizTalk Mapper Patterns: How to Map Name Value Pair to a Hierarchical Schema – Very interesting post that explains how to do this type of mapping with functoids and by XSLT script. […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s