More Validation Rules and Collections questions

More Validation Rules and Collections questions

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


joemorin73 posted on Monday, October 28, 2013

I have searched this issue in the forums for some time and am having a problem getting a best approach answer.

Tech background:  Using CSLA 4.3.14 and .Net 4.0

I have a child collection in an object.  I've made a rule to verify data in the collection.  The rule does not trigger when a child object is modified, including adding or removing.

I understand if this was supported, this could become a VERY messy and expensive event mess.  This doesn't remove the requirement for a solution.  My question is, what would be the best approach?  The closest I saw to a possible solution was this:

http://forums.lhotka.net/forums/p/10687/49898.aspx

Unfortunately, I'm not sure this would work in my scenario as I'm working with heterogeneous collections and I need to verify the collections only contain specific types.

Any help would be appreciated.

skagen00 replied on Monday, October 28, 2013

What is your actual rule you need to enforce?  What sort of "collection data" do you need to verify?

I don't think it needs to be complicated, there is usually a reasonable way of handling this sort of thing.

joemorin73 replied on Monday, October 28, 2013

In my mind, I don't think it's complicated.  Ok, I hope it's not complicated!  Surprise

I have a collection in a root object.  The collection of objects are of varying types.  I need to ensure only specific types are permitted in the object.

I think I may need to write an example for this.

joemorin73 replied on Monday, October 28, 2013

Here is a copy of the rule that is not executing.  It is declared on the root object with the collection.  Like I said, it simply doesn't trigger when the collection is modified as that isn't a property change:

BusinessRules.AddRule(new Csla.Rules.CommonRules.Lambda(ChoicesProperty, (context) =>

{

var sq = (SurveyQuestion)context.Target;

using (sq.BypassPropertyChecks)

{

if (sq.Choices.Where(e => e.GetType() != typeof(SurveyQuestionChoice)).Count() != 0)

context.AddErrorResult("All items in Choices collection must be SurveyQuestionChoice");

}

}), ruleSet);

 

skagen00 replied on Monday, October 28, 2013

You're saying that OnChildChanged in the root object doesn't fire when you add/remove items from the collection?  There are BusinessBase & BusinessListBase objects?  Because OnChildChanged should definitely fire if adding/removing objects from the collection.

OnChildChanged should convey both collection changes as well as property changes as you probably know- but it wouldn't call rules.  BusinessRules.CheckRules(ChoicesProperty) within your OnChildChanged of the root when the object modified is one of the types that it could be.

 

joemorin73 replied on Monday, October 28, 2013

It does fire.  I do see it.  Buuuuuuuuuut, it doesn't tell me which collection fired.  So if I have two collections of the same type in the root object, Do I need to do an equals check?

skagen00 replied on Monday, October 28, 2013

Sure you can just compare it to ReadProperty(Collection1Property) and ReadProperty(Collection2Property) for example, because you get the reference to the object that changed.  Even if it's a child object you could do a Contains on each of those two collections to figure out where it's coming from.

joemorin73 replied on Monday, October 28, 2013

This seems like more work than it should be.  Am I crazy for thinking this?

joemorin73 replied on Monday, October 28, 2013

Ok, I wrote the following to be a generic trigger.  Any issues with the following?

protected override void OnChildChanged(Csla.Core.ChildChangedEventArgs e)

{

base.OnChildChanged(e);

Csla.Core.IPropertyInfo ipiResult = null;

var allChildren = this.FieldManager.GetRegisteredProperties().Where(c => RelationshipTypes.Child == (c.RelationshipType & RelationshipTypes.Child)).ToList();

var testTarget = e.ChildObject;

if (!((Csla.Core.BusinessBase)testTarget).Parent.Equals(this))

{

testTarget = ((Csla.Core.BusinessBase)testTarget).Parent;

}

 

foreach (var p in allChildren)

{

if (((p.RelationshipType & RelationshipTypes.LazyLoad) == RelationshipTypes.LazyLoad) && !FieldManager.FieldExists(p))

continue;

if (ReadProperty(p).Equals(testTarget))

ipiResult = p;

}

PropertyHasChanged(ipiResult);

}

skagen00 replied on Monday, October 28, 2013

Ironically I was going to suggest that you might be able to do a generic OnChildChanged handling in your base class layer that might inherit from CSLA.  It's a matter of preference - for me it's such an uncommon scenario that I'd probably take the negligible performance hit and just check both collections whenever one of them changed (check rules on each), but I can tell you're interested in a "generic" sort of solution.

To me what you have above seems more complicated than to have just checked to see which collection changed (or frankly just say - "I don't care which one, I'll just check the business rules on each of the two collection properties", but that's just me :).

 

JonnyBee replied on Tuesday, October 29, 2013

I terms of writing a "generic" OnChildChanged" event handler that would rerun rules for a child collection/object my suggestion would be:

 

    protected override void OnChildChanged(Csla.Core.ChildChangedEventArgs e)
    {
      var testTarget = e.ChildObject;
      if (!((Csla.Core.BusinessBase)testTarget).Parent.Equals(this))
      {
        testTarget = ((Csla.Core.BusinessBase)testTarget).Parent;
      }

      foreach (var p in FieldManager.GetRegisteredProperties())
      {
        // do not trigger initialization of backing field
        if (!FieldManager.FieldExists(p)) continue;

        if (ReadProperty(p).Equals(testTarget))
          PropertyHasChanged(p);
      }

      base.OnChildChanged(e);
    }

 

The default code i OnChildChanged is this code:

    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected virtual void OnChildChanged(ChildChangedEventArgs e)
    {
      if (_childChangedHandlers != null)
        _childChangedHandlers.Invoke(this, e);
      MetaPropertyHasChanged("IsDirty");
      MetaPropertyHasChanged("IsValid");
      MetaPropertyHasChanged("IsSavable");
    }

joemorin73 replied on Tuesday, October 29, 2013

I see what changes you made and understand all but removing the Where clause on the GetRegisteredProperties.  Wouldn't it be better to limit the list to only Csla objects?

joemorin73 replied on Tuesday, October 29, 2013

Additional note, the following does not work:

if (!((Csla.Core.BusinessBase)testTarget).Parent.Equals(this))
    testTarget = ((Csla.Core.BusinessBase)testTarget).Parent;

BusinessListBase doesn't inherit from BusinessBase.  To complicate matters, BusinessListBase is a generic that can't be cast.  I whipped up a quick reflect that should do the trick.

if (testTarget.GetType().GetProperty("Parent").GetType() == typeof(Csla.Core.IParent))
    if (testTarget.GetType().GetProperty("Parent").GetValue(testTarget, null).Equals(this))
        testTarget = ((Csla.Core.BusinessBase)testTarget).Parent;

 

JonnyBee replied on Tuesday, October 29, 2013

Or you could just cast to IBusinessObject. 

I removed the where clause as I find many developers do not use the RelationShipTypes flag (and it is not mandatory).

joemorin73 replied on Tuesday, October 29, 2013

IBusinessObject doesn't contain Parent in 4.3.x.  I'm limited to the reflection approach. I'm pasting the final code and flagging it as the answer.

  protected override void OnChildChanged(Csla.Core.ChildChangedEventArgs e)
  {
    var testTarget = e.ChildObject;

  if (testTarget.GetType().GetProperty("Parent").GetType() == typeof(Csla.Core.IParent))
    if (testTarget.GetType().GetProperty("Parent").GetValue(testTarget, null).Equals(this))
      testTarget = ((Csla.Core.BusinessBase)testTarget).Parent;

    foreach (var p in this.FieldManager.GetRegisteredProperties())
    {
      if (!FieldManager.FieldExists(p)) continue;

      if (ReadProperty(p).Equals(testTarget))
        PropertyHasChanged(p);
    }

    base.OnChildChanged(e);
  }

Copyright (c) Marimer LLC