Tuesday, July 31, 2007

Refreshing the BRE Facts cache

In my first post I talked a little bit about the FactRetriever object and how it works in concert with a BRE policy. Use of a FactRetriever can help you manage an in-memory cache of a table so that the BRE doesn't have to constantly hit a database for information.

After much debugging, stress, and some research on the part of a coworker, I discovered a crucial bit about how the BRE operates: If a fact (such as a DataTable within a DataSet, or an XML document) is asserted into memory and you wish to refresh that cached version, you must explicitly tell the BRE to retract the fact from memory before asserting it again. Failure to do this will lead to some really strange behavior that will have you debugging in circles.

Depending on your environment and the type of fact you have asserted, there are a couple of ways of doing this. You can call RuleEngine.Clear(), but this halts all BRE execution, cancels all rule firings, and removes all facts from the cache. I'm sure there's a situation where this is desirable, but the odds are that it's not yours. This will clear everything from the BRE's memory.

What you're probably looking for is RuleEngine.Retract() or RuleEngine.RetractByType(). I believe that in the case of a DataTable or DataConnection, these two methods accomplish the same thing, since a DataTable or DataConnection stored in the BRE memory is unique and there can only be one instance of it. Since our goal was to retract a DataTable, we didn't do a lot of research into retracting other types of facts.

Using RetractByType() took a little while to figure out: there is no documentation out there anywhere that we could find, and no extended Intellisense documentation. It took a little experimentation and some creativity to figure out how to do what we wanted. RetractByType() takes a single argument, a FactType object. FactType is abstract, but has a couple of descendants, one of them being DataRowType. DataRowType's constructor takes two strings: the name of a table, and the name of a dataset. These values are what defines a "table type" within the BRE: If you have a table with the Table property set to "ABC" inside a DataSet with the DataSetName property set to "XYZ", that's the only "ABC" "XYZ" table that can be in the BRE's memory at one time. The BRE keys off of those name values when you make references to the table within rules or vocabularies.

In order to create a type identifier so that the BRE can retract your fact for you, all you have to do is create a new DataRowType with the table name and DataSet name correctly specified, and hand that object to RetractByType. The following is an example - this is the first code that runs once the FactRetriever has determined that the cache does indeed need to be refreshed:

if (null != factsHandleIn)
{
engine.RetractByType(new DataRowType("MyTableName", "MyDataSetName"));

}

The reason that I check to see if factsHandleIn is null is because my FactRetriever returns a value as factsHandleOut that gets handed in again as factsHandleIn on the next call. If factsHandleIn is null, that means that this is the first time the FactRetriever has run since restarting its host instance, so there's nothing in memory to Retract anyway. The next thing that

Mixed in with some logic that determines when the cache should be refreshed, you can use this control over retracting/asserting facts to keep an efficient cache in the BRE's memory. Our FactRetriever uses DateTimes and values in the registry to determine if the cache needs to be refreshed - it can be set to refresh automatically on an interval (run the policy and record the time. If the policy runs again and the time span between the last update and now is X seconds, refresh again) or by a user-override (if a user edited the table, he can set a value in the registry to DateTime.Now, and the next time the policy runs it will see a newer value than the last value stored in that registry node and will refresh).

No comments: