The BizTalk business rules engine is a very powerful asset in an integration specialist’s toolbox, but it can be tricky when you are trying to operate with complex schemas as you try to get your head around how it works.  The problem I will try to illustrate here is that the default schema vocabulary definitions will not allow you to spread your conditions and actions across different records in an XML schema if they are both contained within a single instance of a repeating reccord.

For this example, lets say that we want to evaluate a batch of customers, and for those that are Infinity years old (ok, not the best example) we are going to set their AwardsLevel to Gold.  We are going to deal with the below XML schema (note that RegularCustomer is unbounded and that Age is optional) and XML input file.

Of course Chuck Norris is Infinity!

Let’s add some vocabulary definitions to get a customer’s age and to set his awards level.  To start with let’s just select the elements we’re interested in from the schema view when creating the definitions.

It’s now time to define our policy as below.

We need to test out the policy we’ve just created so lets save it first.  Now right click on the policy version and choose test policy.  You will need to click on the schema listed under XML Documents and click the add instance button, choosing to point towards our test XML file.

When you execute the test you’ll see that the results are somewhat unexpected, with every customer being awarded gold status, even the ones younger than Infinity.

It’s time to do some investigation.  I’ve found the quickest way to understand what makes a policy tick is to export it using the Business Rules Engine Deployment Wizard (you’ll find the shortcut under your start menu in the BizTalk Server folder) and to inspect the XML file.

The first thing of note is that in the bindings section there are two XML document instances, each with their own XPath selectors defined.  The first selector is to the CustomerDetails record and the second to the AwardsDetails record.  When you dig into the GoldAwards rule you’ll notice that the xmldocumentmember element in the <if> record has a xmldocumentref attribute of xml_31 (the CustomerDetails selector) and the xmldocumentmember element in the <then> record has a xmldocumentref attribute of xml_32 (the AwardsDetails record).  It’s pretty obvious that our if and then statement are not scoped to the same RegularCustomer record, and the path to enforcing the scope is to ensure that the xpath statements executed in the <if> and <then> records of our rule use the same selector.

So this time lets revisit our vocabulary definitions.  By default the XML selector and XML field values for GetAge and SetAwardsLevel look like the below.

GetAge Selector – /*[local-name()=’RegularCustomers’ and namespace-uri()=’http://BusinessRulesTest.BusinessRuleSchema’%5D/*%5Blocal-name()=’RegularCustomer&#8217; and namespace-uri()=”]/*[local-name()=’CustomerDetails’ and namespace-uri()=”]

GetAge Field – *[local-name()=’Age’ and namespace-uri()=”]

SetAwardsLevel Selector – /*[local-name()=’RegularCustomers’ and namespace-uri()=’http://BusinessRulesTest.BusinessRuleSchema’%5D/*%5Blocal-name()=’RegularCustomer&#8217; and namespace-uri()=”]/*[local-name()=’AwardsDetails’ and namespace-uri()=”]

SetAwardsLevel Field – *[local-name()=’AwardsLevel’ and namespace-uri()=”]

Let’s edit the definitions (you’ll need to make a new vocabulary version) so that the  XML selector and XML field values for GetAge and SetAwardsLevel look like the below (note that the selectors are now the same being scoped to the RegularCustomer record while the field digs into the required child records).

GetAge Selector – /*[local-name()=’RegularCustomers’ and namespace-uri()=’http://BusinessRulesTest.BusinessRuleSchema’%5D/*%5Blocal-name()=’RegularCustomer&#8217; and namespace-uri()=”]

GetAge Field – *[local-name()=’CustomerDetails’ and namespace-uri()=”]/*[local-name()=’Age’ and namespace-uri()=”]

SetAwardsLevel Selector – /*[local-name()=’RegularCustomers’ and namespace-uri()=’http://BusinessRulesTest.BusinessRuleSchema’%5D/*%5Blocal-name()=’RegularCustomer&#8217; and namespace-uri()=”]

SetAwardsLevel Field – *[local-name()=’AwardsDetails’ and namespace-uri()=”]/*[local-name()=’AwardsLevel’ and namespace-uri()=”]

Let’s change the rule such that it uses the new version of our vocabulary (note that a rule refers to a specific version of a vocabulary definition, it will not automatically choose the latest version of the vocabulary).  If we give it a test now we’ll see that only the RegularCustomer records with an Age of Infinity have Gold status :).  You’ll also see if you export the policy to XML that there is now only one xmldocument record in the bindings section and that both the <if> and the <then> use this instance.

Chuck Norris = Gold

There is one last important thing to note.  What if we passed in a RegularCustomer record which for whatever reason did not contain an Age element at all (remember that it is defined as an optional element)?  You’ll see that the rule fails and throws an exception, and thus the entire policy fails to execute with the below error.

RuleEngineRuntimeFieldNotDefinedException –  Field “*[local-name()=’CustomerDetails’ and namespace-uri()=”]/*[local-name()=’Age’ and namespace-uri()=”]” does not exist in XML document “BusinessRulesTest.BusinessRuleSchema”, selector “/*[local-name()=’RegularCustomers’ and namespace-uri()=’http://BusinessRulesTest.BusinessRuleSchema’%5D/*%5Blocal-name()=’RegularCustomer&#8217; and namespace-uri()=”]”.

This here blog post (I suggest that everyone that intends to use the BRE read this as you will undoubtedly hit this problem sooner or later) suggests that we can get around this problem by editing the selector to ensure that the relevant fields exist.  In this case we need to ensure that the Age and AwardsLevel (might as well cater for this too) fields exist.  We can do this by editing the xpath selectors to look like below.

/*[local-name()=’RegularCustomers’ and namespace-uri()=’http://BusinessRulesTest.BusinessRuleSchema’%5D/*%5Blocal-name()=’RegularCustomer&#8217; and namespace-uri()=”][CustomerDetails/Age and AwardsDetails/AwardsLevel]

The catch here is that if you don’t update the xpath selectors for both the GetAge and SetAwardsLevel definitions then you’ll find that you are back to square one with all the RegularCustomer records getting a Gold AwardsLevel.  Thus one can conclude that the xpath selectors must match exactly when attempting to define a scope between rule actions and conditions.