Chicken vs. Egg problem with AddInstanceBusinessRules

Chicken vs. Egg problem with AddInstanceBusinessRules

Old forum URL: forums.lhotka.net/forums/t/7735.aspx


rsbaker0 posted on Tuesday, October 06, 2009

I haven't had a need for these until now, but I'm confused by the fact that BusinessBase() calls AddInstanceBusinessRules in its constructor.

Since I don't think it is possible for the derived class to populate any properties or fields before the base class constructor is called, how can an override of AddInstanceBusinessRules() make a context-based decision as to what rules apply just to that instance. Am I misunderstanding the intent of these?

I've worked around it for now by moving the rule-adding code to a shared method that I manually call immediately after constructing the object with desired initial state and it seems to work, but this seems like a hack. Other than that, however, it seems to be providing the desired behavior.

(The use case here is a tree-style display of 300 or so configuration or option values that are all basically identical except for their name and how they are validated. An alternative would be to derive a unique class for each option value just so it could have its own rule, but that seems like overkill)

RockfordLhotka replied on Tuesday, October 06, 2009

I'll probably remove per-instance rules in 4.0.

They were the model in 2.0, but had serious perf/resource issues, which is why I added the per-type concepts. Andres lobbied me to remove per-instance at the time, and I think he was right, but I left them in for "backward compatibility".

They exist as they did in 2.0 though, and really don't address a valid scenario in my view. Clearly almost no one uses them, because if anyone did use them they'd run into the issues you describe - and this is the first complaint I've had in years :)

The right answer, imo, is to create per-type rules that are smart enough to be context-sensitive based on the state of the object. In other words, there's nothing you can do with per-instance rules that you can't do with a smarter per-type rule.

rsbaker0 replied on Tuesday, October 06, 2009

RockfordLhotka:

They exist as they did in 2.0 though, and really don't address a valid scenario in my view. Clearly almost no one uses them, because if anyone did use them they'd run into the issues you describe - and this is the first complaint I've had in years :)


The right answer, imo, is to create per-type rules that are smart enough to be context-sensitive based on the state of the object. In other words, there's nothing you can do with per-instance rules that you can't do with a smarter per-type rule.



I don't disagree -- this is the first time I thought I could use them and I was trying to do the "right" thing (which it turns out was wrong :)

I could probably (for this use case) simply have a single per type rule that calls a delegate exposed by the object that uses the same signature and achieve the equivalent to what I get now (serialization issues with delegates aside).

Fintanv replied on Wednesday, October 07, 2009

RockfordLhotka:

I'll probably remove per-instance rules in 4.0.

Nooooooo!

Please don't remove them.  They may now be edge cases, but they still can serve a purpose.  I have a dynamic system where the users create data collection flows.  They can add data collection points, number of samples, datatype, etcetera and can set up rules (depending on the actual datatype) such as min/minlength, max/maxlength, and so on. 

When the sample business object is instantiated for actual data entry, I use the database stored rule metadata to create and add the instance rules to the business objects.  It works great but I do not think that I could do the same thing with the static rules alone.  The individual business objects for a 'sample' are the same, but may have different rules in effect based not only on the datatype, but also by the specific type of the item you are collecting data on (hierarchical rules), and they type of measurement.

RockfordLhotka replied on Wednesday, October 07, 2009

How do you get around the chicken-and-egg problem in your implementation?

rsbaker0 replied on Wednesday, October 07, 2009

RockfordLhotka:
How do you get around the chicken-and-egg problem in your implementation?


It'a flawed, but essentially I just exposed my AddInstanceBusinessRules method via a helper function that I call just once after I instantiate the object.

If only there was some way for CSLA to defer the call until sometime after the constructor was called, I would never have had an issue.

In my case, the actual AddInstanceBusinessRules method is looking for a particular property and is a NOP otherwise, and since I can't set it in the constructor before CSLA calls the method, the CSLA call does nothing.

I'll vote with Fintanv that these may indeed have a niche. They are working out quite well in this one case when I need them.

RockfordLhotka replied on Wednesday, October 07, 2009

I could move the call to Initialize(), or not do it at all - leaving it to you to do the call if you care (which most people wouldn't).

Either one would be a breaking change for anyone using this niche feature (probably just you two :) ), so may not be worth doing.

rsbaker0 replied on Wednesday, October 07, 2009

RockfordLhotka:

I could move the call to Initialize(), or not do it at all - leaving it to you to do the call if you care (which most people wouldn't).


Either one would be a breaking change for anyone using this niche feature (probably just you two :) ), so may not be worth doing.



Ouch! :)

Actually, I just looked in the 3.5 code and Initialize() is still called in the BusinessBase constructor. So, that wouldn't be a substantive change.

Perhaps in DataPortal_Create()? This does the first call to ValidationRules.CheckRules() anyway.

A possible non-breaking change would be to expose _instanceRules (or at least the Count) via a protected property. Then derived classes could implement deferred creation of the rules as necessary.

RockfordLhotka replied on Thursday, October 08, 2009

The only real solution is probably for CSLA to stop calling the method at
all, and for you to call it when you are ready.

You'd need to call it after you've initialized your object in
DataPortal_Create and _Fetch, and also in OnDeserialized().

That'd allow you to get your object state the way you want it before loading
appropriate rules.

Very much a breaking change though...

Fintanv replied on Thursday, October 08, 2009

RockfordLhotka:
How do you get around the chicken-and-egg problem in your implementation?

I ignore it! ;-)

I add the rules later in the process.

The rules are added to child objects that are nested down a couple of levels in my object hierarchy.  As the child is added to its parent collection I query a read-only collection of rule metadata for the sub-set that match the childs criteria.  I then iterate over that subset and attach the rule using a helper method.  (IValidation just exposes the Csla.Validation.ValidationRules for the object).

        public static void AddDynamicRule(IValidation item, Csla.Core.IPropertyInfo property, Validation.ItemDetailRuleInfo rule)
        {
            string method = string.Format("Add{0}Rule", rule.RuleType.ToString()); // rule type is 'Min', 'Max', 'MaxLength', 'MinLength', 'In', 'Regex' etc.

                MethodInfo mi = typeof(MyApp.Library.Validation.CommonRules).GetMethod(method, BindingFlags.NonPublic | BindingFlags.Static);
                if (mi != null)
                    if (mi.IsGenericMethod)
                        mi.MakeGenericMethod(rule.Datatype).Invoke(null, new object[] { item, property, rule });
                    else
                        mi.Invoke(null, new object[] { item, property, rule });
        }

 

If a property affecting the rule choice is changed, then I can clear out the rules, and re-add them.  The clearing of the rules is carried out by a method that gets down and dirty with reflection:

        public static void ClearInstanceRules(this Csla.Validation.ValidationRules item)
        {
            // get the rule manager
            MethodInfo mi = item.GetType().GetMethod("GetInstanceRules", BindingFlags.Instance | BindingFlags.NonPublic);
            var ruleManager = mi.Invoke(item, new object[] { true });

            // clear the instance rules
            PropertyInfo pi = ruleManager.GetType().GetProperty("RulesDictionary", BindingFlags.Instance | BindingFlags.NonPublic);
            var dictionary = pi.GetValue(ruleManager, null);
            MethodInfo dmi = dictionary.GetType().GetMethod("Clear", BindingFlags.Instance | BindingFlags.Public);
            dmi.Invoke(dictionary, null);

            // get the broken rules variable and re-set it to null
            FieldInfo fi = item.GetType().GetField("_rulesToCheck", BindingFlags.NonPublic | BindingFlags.Instance);
            fi.SetValue(item, null);

            // get the broken rules variable and re-set it to null
            fi = item.GetType().GetField("_brokenRules", BindingFlags.NonPublic | BindingFlags.Instance);
            fi.SetValue(item, null);

            // recheck the remaining (per-type) rules
            item.CheckRules();
        }

rsbaker0 replied on Thursday, October 08, 2009

Fintanv:
...If a property affecting the rule choice is changed, then I can clear out the rules, and re-add them...


In my case, the set of instance rules desired is known at the time the object is constructed via information passed to the constructor, which seemed simple enough, other than AddInstanceBusinessRules being called before this information is available.

(Kudos to you for figuring out how to implement dynamic instance rules...)

Copyright (c) Marimer LLC