Compartilhar via


Sudoku Validator, part I - Rules and Collections

When I go out and I talk to partners and customers about WF there is a lot of interest in leveraging the rules capabilities.  Whether they are looking to have declarative rules inside their workflows, or by executing complex policy against a set of custom objects in any .NET code, there's a lot about rules to like. 

I'm working on a sample which uses Rules to validate a Sudoku board, namely from Sudoku sample on the community site.

Now, there are plenty of complicated ways to determine if a Sudoku configuration is valid (and plenty of other ways to solve the Sudoku [including Don Knuth's Dancing Links approach to solve the Exact Cover problem for Sudoku]).  But I'm going to focus on a relatively simple, validate rows, validate columns and validate boxes approach. 

I'm going to need to operate my rules across a collection, namely a 2-dimensional array of integers.  So, how do we operate across a collection?

Searching in the SDK, which is full of fantastic information, leads us to the "Using Rule Conditions in Workflows" section.  In there you'll find the "Collection Processing" section that outlines how you can process over collections.  It goes a little something like this (in descending priority):

  • Rule 1: Create an initialization rule, with a higher priority than any of the rules that follow.  This is where you can get the enumerator for the collection.
  • Rule 2: Get the current object (this is where we check to make sure we should keep on enumerating by having enumerator.MoveNext() as our condition.  To re-evaluate this rule, we either need to modify enumerator, or we need to explicitly call Update("this/enumerator") in order to cause this rule to re-execute.
  • Rule 3: Execute rules against this instance of the item
  • Rule 4: Lowest priority, we need to make sure this gets evaluated every time, so something like this.currentInstance == this.currentInstance is a good bet.  Because we update currentInstance, this rule will eventually re-fire, but due to its lower priority will execute after the actions on the current instance.  The condition of this rule is that we update the enumerator (the fact that rule 2 depends upon, causing us to loop back up to rule 2 and begin executing on our next instance.

Now, I have an array.  Sure, I can get the enumerator from this, but I'd like to use an integer which I update to navigate through this array.  This is important to me because I need to know where I am at in the process (the 1st element or the 5th element) in order to convey some relevant information out to the user.

For an array, we've got a little simpler pattern that incorporates some shortcuts, we could certainly do the approach above, and if we're sure of our incoming variables (namely the integer we use to keep track of our current position), we can do it in one rule.  The rule looks like this:

if (i < ItemCount)

THEN

total = total + items[i]

i = i + 1 <== this is what causes the rule to then re-evaluate itself due to chaining

We have an initial rule, with a priority of 2 set to initialize i to 0.  But wait, you may say, why does my class need to have this iterator.  The short story, it doesn't, but you have to have it somewhere, so what I have done is create a rule helper class that contains an instance of the class I am interested in executing the ruleset on, along with whatever "support" variables I need.  So, we use the rules engine chaining capabilities in our second rule, with priority 1, to force the re-execution of the rule as many times as we need.  And thus, we iterate over our simple array, the collection we all start from!

Ok, you may say, but I want a sample of this working when I truly have a collection of custom objects.  Let's do that as well.  There's one tricky thing to note here:  The Intell-sense like interface sometimes makes it hard to consistently case stuff.  I spent a while chasing down why my ruleset was only executing twice when I had 30 objects in the list.  Consistently casing things made that work right.  The problem that I had was that the final rule, was cased to the member variables, all lower case.  The rule where I set the current item to the next one in the stack was cased to the property.  When I changed this, because the symbol names didn't match up (the facts, if you will) the engine did not know to re-execute my final rule, which in turn did not call update to cause everything to re-evaluate.  I also had some odd behavior with a typed IEnumerator (from System.Collection.Generics), but it appears now that things are working fine after discovering and correcting the cAsInG issues.  So, like I said, pay attention to your casing, especially if you follow a variableName / VariableName naming scheme for C# private variables and properties.

Also, check out Moustafa's post on how you can use rule tracing to see what rules executed, this is what clued me in to what was and wasn't happening.

In the meantime, you can check out the sample that I have posted to the community site here.

Steps to get the application to work:

  • Download and extract the RulesWithCollectionSample
  • Open the RulesWithCollectionSample solution and build it
  • Download and extract the External RuleSet Toolkit, and create a rules database.
  • Import the rules from the .rules file in the RulesWithCollectionSample folder (you will need to point it to the assemblies of the RulesWithCollectionSample solution that you just built)
  • Save this to the rules database
  • Make sure the app.config file points to the right rules database.
  • Hit F5 to run, and you will be able to add numbers in one of two ways (via an array or a collection of objects)

I look forward to your feedback!

Comments

  • Anonymous
    November 08, 2006
    I thought I understood how the WF Rules iterated over a collection. Isn't that always how it goes - you