Some of the products we list have 18-24 permeations and we want to show customers how many we might have in stock currently. Luckily with XmlPackages, this is easy to do.
First, let’s look at the result:

Setting Up the Database
The first step to our result is to get the current stock information into our XmlPackage via a Query. There are two way to do this, rowsets and XML. A rowset is the common ‘table’ of information of from the database. This is how most other packages work. When looking for help on using XML result sets for Interprise, I doubt there are any existing samples, so I figured I might as well be first.
** Warning ** This method is complex and may not be the “best” method, but it is the one I went with.
First, how do we get SQL to produce well structured XML data. Well, if you are rocking SQL Server 2005 or latter and understand the T-SQL syntax, this is simple with the ‘FOR XML’ keywords. Here is my stored procedure for matrix items using up to 3 attributes (Sorry for the scroll bars, but it is crazy long):
- CREATE PROCEDURE [dbo].[ttx_GetItemInventoryXml]
- @ItemCode nvarchar(30)
- AS
- BEGIN
- SET NOCOUNT ON;
-
- DECLARE @AttributeCount int = 1
- DECLARE @Result nvarchar(max)
-
- SELECT @AttributeCount = COUNT(*)
- FROM InventoryItem i WITH (NOLOCK)
- INNER JOIN InventoryAttribute ia WITH (NOLOCK) ON ia.ItemCode = i.ItemCode
- WHERE i.ItemCode = @ItemCode
-
- IF (@AttributeCount = 0) BEGIN
- SELECT '' -- This is built for Matrix items only right now
- END
-
- IF (@AttributeCount = 1) BEGIN
- DECLARE @MyTempTable1 TABLE
- (
- Tag int
- , Parent int
- , [Item!1!ItemCode] nvarchar(30)
- , [Attribute1!2!Attribute1] nvarchar(255)
- , [Attribute1!2!AttributeCode1] nvarchar(255)
- , [Attribute2!3] decimal
- );
-
- insert into @MyTempTable1
- SELECT 1 AS 'Tag', NULL AS 'Parent'
- , imi.ItemCode AS [Item!1!ItemCode]
- , NULL AS [Attribute1!2!Attribute1]
- , NULL AS [Attribute1!2!AttributeCode1]
- , NULL AS [Attribute2!2]
-
- from InventoryItem ii WITH (NOLOCK)
- LEFT JOIN InventoryStockTotal isv WITH (NOLOCK) ON ii.ItemCode = isv.ItemCode
- LEFT JOIN InventoryMatrixItem imi WITH (NOLOCK) ON ii.ItemCode = imi.MatrixItemCode
- WHERE imi.ItemCode = @ItemCode
-
- UNION
-
- SELECT 2 AS 'Tag', 1 AS 'Parent', imi.ItemCode
- , imi.Attribute1, imi.AttributeCode1
- , isv.UnitsInStock
- from InventoryItem ii WITH (NOLOCK)
- LEFT JOIN InventoryStockTotal isv WITH (NOLOCK) ON ii.ItemCode = isv.ItemCode
- LEFT JOIN InventoryMatrixItem imi WITH (NOLOCK) ON ii.ItemCode = imi.MatrixItemCode
- WHERE imi.ItemCode = @ItemCode
-
- ORDER BY [Item!1!ItemCode]
- , [Attribute1!2!Attribute1]
- , [Attribute1!2!AttributeCode1]
-
- SET @Result = (SELECT * FROM @MyTempTable1 FOR XML EXPLICIT)
- SELECT @Result
- END
-
- IF (@AttributeCount = 2) BEGIN
-
- DECLARE @MyTempTable2 TABLE
- (
- Tag int
- , Parent int
- , [Item!1!ItemCode] nvarchar(30)
- , [Attribute1!2!Attribute1] nvarchar(255)
- , [Attribute1!2!AttributeCode1] nvarchar(255)
- , [Attribute2!3!Attribute2] nvarchar(255)
- , [Attribute2!3!AttributeCode2] nvarchar(255)
- , [Attribute3!4] decimal
- );
-
- insert into @MyTempTable2
- SELECT 1 AS 'Tag', NULL AS 'Parent'
- , imi.ItemCode AS [Item!1!ItemCode]
- , NULL AS [Attribute1!2!Attribute1]
- , NULL AS [Attribute1!2!AttributeCode1]
- , NULL AS [Attribute2!3!Attribute2]
- , NULL AS [Attribute2!3!AttributeCode2]
- , NULL AS [Attribute3!3]
-
- from InventoryItem ii WITH (NOLOCK)
- LEFT JOIN InventoryStockTotal isv WITH (NOLOCK) ON ii.ItemCode = isv.ItemCode
- LEFT JOIN InventoryMatrixItem imi WITH (NOLOCK) ON ii.ItemCode = imi.MatrixItemCode
- WHERE imi.ItemCode = @ItemCode
-
- UNION
-
- SELECT 2 AS 'Tag', 1 AS 'Parent', imi.ItemCode
- , imi.Attribute1, imi.AttributeCode1
- , NULL, NULL
- , NULL
- from InventoryItem ii WITH (NOLOCK)
- LEFT JOIN InventoryStockTotal isv WITH (NOLOCK) ON ii.ItemCode = isv.ItemCode
- LEFT JOIN InventoryMatrixItem imi WITH (NOLOCK) ON ii.ItemCode = imi.MatrixItemCode
- WHERE imi.ItemCode = @ItemCode
-
- UNION
-
- SELECT 3 AS 'Tag', 2 AS 'Parent', imi.ItemCode
- , imi.Attribute1, imi.AttributeCode1
- , imi.Attribute2, imi.AttributeCode2
- , isv.UnitsInStock
- from InventoryItem ii WITH (NOLOCK)
- LEFT JOIN InventoryStockTotal isv WITH (NOLOCK) ON ii.ItemCode = isv.ItemCode
- LEFT JOIN InventoryMatrixItem imi WITH (NOLOCK) ON ii.ItemCode = imi.MatrixItemCode
- WHERE imi.ItemCode = @ItemCode
-
- ORDER BY [Item!1!ItemCode]
- , [Attribute1!2!Attribute1]
- , [Attribute1!2!AttributeCode1]
- , [Attribute2!3!Attribute2]
-
- SET @Result = (SELECT * FROM @MyTempTable2 FOR XML EXPLICIT)
- SELECT @Result
- END
-
- IF (@AttributeCount = 3) BEGIN
-
- DECLARE @MyTempTable3 TABLE
- (
- Tag int
- , Parent int
- , [Item!1!ItemCode] nvarchar(30)
- , [Attribute1!2!Attribute1] nvarchar(255)
- , [Attribute1!2!AttributeCode1] nvarchar(255)
- , [Attribute2!3!Attribute2] nvarchar(255)
- , [Attribute2!3!AttributeCode2] nvarchar(255)
- , [Attribute3!4!Attribute3] nvarchar(255)
- , [Attribute3!4!AttributeCode3] nvarchar(255)
- , [Attribute3!4] decimal
- );
-
- insert into @MyTempTable3
- SELECT 1 AS 'Tag', NULL AS 'Parent'
- , imi.ItemCode AS [Item!1!ItemCode]
- , NULL AS [Attribute1!2!Attribute1]
- , NULL AS [Attribute1!2!AttributeCode1]
- , NULL AS [Attribute2!3!Attribute2]
- , NULL AS [Attribute2!3!AttributeCode2]
- , NULL AS [Attribute3!4!Attribute3]
- , NULL AS [Attribute3!4!AttributeCode3]
- , NULL AS [Attribute3!4]
-
- from InventoryItem ii WITH (NOLOCK)
- LEFT JOIN InventoryStockTotal isv WITH (NOLOCK) ON ii.ItemCode = isv.ItemCode
- LEFT JOIN InventoryMatrixItem imi WITH (NOLOCK) ON ii.ItemCode = imi.MatrixItemCode
- WHERE imi.ItemCode = @ItemCode
-
- UNION
-
- SELECT 2 AS 'Tag', 1 AS 'Parent', imi.ItemCode
- , imi.Attribute1, imi.AttributeCode1
- , NULL, NULL
- , NULL, NULL
- , NULL
- from InventoryItem ii WITH (NOLOCK)
- LEFT JOIN InventoryStockTotal isv WITH (NOLOCK) ON ii.ItemCode = isv.ItemCode
- LEFT JOIN InventoryMatrixItem imi WITH (NOLOCK) ON ii.ItemCode = imi.MatrixItemCode
- WHERE imi.ItemCode = @ItemCode
-
- UNION
-
- SELECT 3 AS 'Tag', 2 AS 'Parent', imi.ItemCode
- , imi.Attribute1, imi.AttributeCode1
- , imi.Attribute2, imi.AttributeCode2
- , NULL, NULL
- , NULL
- from InventoryItem ii WITH (NOLOCK)
- LEFT JOIN InventoryStockTotal isv WITH (NOLOCK) ON ii.ItemCode = isv.ItemCode
- LEFT JOIN InventoryMatrixItem imi WITH (NOLOCK) ON ii.ItemCode = imi.MatrixItemCode
- WHERE imi.ItemCode = @ItemCode
-
- UNION
- SELECT 4 AS 'Tag', 3 AS 'Parent', imi.ItemCode
- , imi.Attribute1 , imi.AttributeCode1
- , imi.Attribute2, imi.AttributeCode2
- , imi.Attribute3, imi.AttributeCode3
- , isv.UnitsInStock
- from InventoryItem ii WITH (NOLOCK)
- LEFT JOIN InventoryStockTotal isv WITH (NOLOCK) ON ii.ItemCode = isv.ItemCode
- LEFT JOIN InventoryMatrixItem imi WITH (NOLOCK) ON ii.ItemCode = imi.MatrixItemCode
- WHERE imi.ItemCode = @ItemCode
- ORDER BY [Item!1!ItemCode]
- , [Attribute1!2!Attribute1]
- , [Attribute1!2!AttributeCode1]
- , [Attribute2!3!Attribute2]
- , [Attribute3!4!Attribute3]
-
- SET @Result = (SELECT * FROM @MyTempTable3 FOR XML EXPLICIT)
- SELECT @Result
- END
-
- -- We don't support more than 3 attributes, but it is very easy to extend
- -- this by following the pattern above.
- IF (@AttributeCount > 3) BEGIN
- SELECT ''
- END
- END
And the output is a single row with a single column that looks like the following:
- <Item ItemCode="ITEM-000001">
- <Attribute1 Attribute1="Black" AttributeCode1="Color">
- <Attribute2 Attribute2="33 deg" AttributeCode2="Hardness">
- <Attribute3 Attribute3="1.8mm" AttributeCode3="Thickness">15.000000</Attribute3>
- <Attribute3 Attribute3="2.0mm" AttributeCode3="Thickness">0.000000</Attribute3>
- <Attribute3 Attribute3="2.3mm" AttributeCode3="Thickness">0.000000</Attribute3>
- </Attribute2>
- <Attribute2 Attribute2="37 deg" AttributeCode2="Hardness">
- <Attribute3 Attribute3="1.8mm" AttributeCode3="Thickness">0.000000</Attribute3>
- <Attribute3 Attribute3="2.0mm" AttributeCode3="Thickness">0.000000</Attribute3>
- <Attribute3 Attribute3="2.3mm" AttributeCode3="Thickness">0.000000</Attribute3>
- </Attribute2>
- <Attribute2 Attribute2="41 deg" AttributeCode2="Hardness">
- <Attribute3 Attribute3="1.8mm" AttributeCode3="Thickness">1.000000</Attribute3>
- <Attribute3 Attribute3="2.0mm" AttributeCode3="Thickness">0.000000</Attribute3>
- <Attribute3 Attribute3="2.3mm" AttributeCode3="Thickness">0.000000</Attribute3>
- </Attribute2>
- </Attribute1>
- <Attribute1 Attribute1="Red" AttributeCode1="Color">
- <Attribute2 Attribute2="33 deg" AttributeCode2="Hardness">
- <Attribute3 Attribute3="1.8mm" AttributeCode3="Thickness">0.000000</Attribute3>
- <Attribute3 Attribute3="2.0mm" AttributeCode3="Thickness">0.000000</Attribute3>
- <Attribute3 Attribute3="2.3mm" AttributeCode3="Thickness">0.000000</Attribute3>
- </Attribute2>
- <Attribute2 Attribute2="37 deg" AttributeCode2="Hardness">
- <Attribute3 Attribute3="1.8mm" AttributeCode3="Thickness">0.000000</Attribute3>
- <Attribute3 Attribute3="2.0mm" AttributeCode3="Thickness">0.000000</Attribute3>
- <Attribute3 Attribute3="2.3mm" AttributeCode3="Thickness">0.000000</Attribute3>
- </Attribute2>
- <Attribute2 Attribute2="41 deg" AttributeCode2="Hardness">
- <Attribute3 Attribute3="1.8mm" AttributeCode3="Thickness">0.000000</Attribute3>
- <Attribute3 Attribute3="2.0mm" AttributeCode3="Thickness">0.000000</Attribute3>
- <Attribute3 Attribute3="2.3mm" AttributeCode3="Thickness">0.000000</Attribute3>
- </Attribute2>
- </Attribute1>
- </Item>
Updating our XmlPackage
Now, how do we use that block of xml in our XmlPackages? Well first, I will be modifying XmlPackages\product.matrixproduct.xml.config.
Make sure you backup your XmlPackages before changing them! You have been warned.
First, lets add our Query to the Package element right before our PackageTransform element:
- <query name="Inventory" rowElementName="Item" retType="xml">
- <sql>
- <![CDATA[
- exec ttx_GetItemInventoryXml @ItemCode
- ]]>
- </sql>
- <queryparam paramname="@ItemCode" paramtype="runtime" requestparamname="ItemCode" sqlDataType="nvarchar" defvalue="" validationpattern=""/>
- </query>
What this does is call our stored procedure passing in the ItemCode. Make special note of the retType attribute, this is what tells the XmlPackage to view the result set as XML.
Now to use that XML data, here is a xsl:template showing how to do two common outputs, a list and a table. The XSL is hardcoded to use 3 attributes, but you can easily modify it to use any number of attributes instead. If anyone can find a better way to ‘layout’ the table without using 10 xsl:for-each elements, please let me know.
- <xsl:template name="InventoryTable">
- <div class="ttxStockTable">
-
- <!-- Uncomment this for a simple listing of products -->
- <!--<xsl:for-each select="/root/Inventory/Item/Attribute1">
- <xsl:for-each select="Attribute2">
- <xsl:for-each select="Attribute3">
- <xsl:value-of select="../../@AttributeCode1"/>: <xsl:value-of select="../../@Attribute1"/>,
- <xsl:value-of select="../@AttributeCode2"/>: <xsl:value-of select="../@Attribute2"/>,
- <xsl:value-of select="@AttributeCode3"/>: <xsl:value-of select="@Attribute3"/>,
- <xsl:value-of select="." /><br/>
- </xsl:for-each>
- </xsl:for-each>
- </xsl:for-each>-->
-
- <!-- Uncomment this for a pretty table of products -->
- <table>
- <tbody>
- <xsl:for-each select="/root/Inventory/Item[1]">
- <tr>
- <th></th>
- <xsl:for-each select="Attribute1" >
- <th class="headerName">
- <xsl:attribute name="colspan">
- <xsl:value-of select="count(Attribute2)" />
- </xsl:attribute>
- <xsl:value-of select="@Attribute1" />
- </th>
- </xsl:for-each>
- </tr>
- </xsl:for-each>
-
- <xsl:for-each select="/root/Inventory/Item[1]">
- <tr>
- <th></th>
- <xsl:for-each select="/root/Inventory/Item[1]/Attribute1">
- <xsl:for-each select="Attribute2" >
- <th class="headerName">
- <xsl:value-of select="@Attribute2" />
- </th>
- </xsl:for-each>
- </xsl:for-each>
- </tr>
- </xsl:for-each>
-
- <xsl:for-each select="/root/Inventory/Item[1]/Attribute1[1]/Attribute2[1]/Attribute3">
- <tr>
- <xsl:variable name="CurrentAtt3" select="@Attribute3" />
- <td class="sizeLabel">
- <xsl:value-of select="@Attribute3"/>
- </td>
- <xsl:for-each select="/root/Inventory/Item/Attribute1">
- <xsl:for-each select="Attribute2">
- <xsl:for-each select="Attribute3[@Attribute3=$CurrentAtt3]">
- <!-- Uncomment this to give exact stock levels instead of hints -->
- <!--<td><xsl:value-of select="." /></td>-->
- <xsl:choose>
- <xsl:when test=". = 1">
- <td class="StockHint StockHint_LowStock">
- Low Stock
- </td>
- </xsl:when>
- <xsl:when test=". = 2">
- <td class="StockHint StockHint_InStock">
- In Stock
- </td>
- </xsl:when>
- <xsl:when test=". >= 3">
- <td class="StockHint StockHint_InStock">
- In Stock
- </td>
- </xsl:when>
- <xsl:otherwise>
- <td class="notInStock">
- </td>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:for-each>
- </xsl:for-each>
- </xsl:for-each>
- </tr>
- </xsl:for-each>
- </tbody>
- </table>
- </div>
- </xsl:template>
And finally, lets call that template in our product. I put my right after the description (Line ~130) like so:
- <xsl:value-of select="$pDescription" disable-output-escaping="yes"/>
- <xsl:call-template name="InventoryTable" />
So, that was not too hard, and I hope this gives everyone the courage to start doing awesome stuff with their XmlPackages. There is an endless supply of data out there and you can access it all!