Cascading validation

Posts   
 
    
Posts: 93
Joined: 13-Feb-2008
# Posted on: 19-Feb-2009 23:23:12   

Has anyone developed a mechanism for cascading validation from an aggregate root? In other words calling ValidateEntity on an object and having that intelligently call ValidateEntity on associated entities.

I'm trying to work out a design for this...possibly something like (off the top of my head...)


[AggregateValidationRoot]
public class RootEntity
{
   [AggregateValidationNode]
   public IEntity2 AssociatedEntity{get;set;}

   public IEntity2 OptionalEntity{get;set;}
}

Then in the validator classes pull off some reflection to call validate on the nested objects...

....I'm not sure if declaritive is the way to go...I just don't want to write something like this...


bool IsValid
{
get{
     return this.IsValid
&& this.AssociatedEntity != null
&& this.AssociateEntity.IsValid
&& this.AssociatedEntity.AnotherAssociatedEntity != null
&& this.AssociatedEntity.AnotherAssociatedEntity.IsValid
&& etc, etc...
    }
}

This gets pretty nasty with a large object graph...

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 20-Feb-2009 06:56:20   

What about recursive save? That, in IMHO should sove it. You implement validations for each entity and only the objects related will be validated when the save action takes plcae (ValidateBeforeSave). It isn't nasty and it's natural.

David Elizondo | LLBLGen Support Team
Posts: 93
Joined: 13-Feb-2008
# Posted on: 20-Feb-2009 15:35:30   

That would solve it for creating a new object graph and persisting it to the database. Consider the scenario where I've read an object graph into memory and I need to validate that the data is complete before passing it along to another process. Calling a recursive save is not a natural way to validate in that scenario.

Is there an event that will validate each entity upon fetch? That may work as long as it doesn't cause the fetch to fail or stop on an invalid entity...

Posts: 93
Joined: 13-Feb-2008
# Posted on: 20-Feb-2009 15:59:42   

ValidateEntityAfterLoad

I'll take a look at this override and see if it will work for my scenario.

Posts: 93
Joined: 13-Feb-2008
# Posted on: 20-Feb-2009 16:22:46   

ValidateEntityAfterLoad appears to fire before the object graph is put together i.e. after each entity is populated but before related entities are set. My validation is dependent upon the whole object graph being loaded so unless I'm missing something I don't think this event is the answer to my issue.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 22-Feb-2009 20:13:59   

Yep, that method wont work for your purposes. As the entities are stateless, determining when a entities totally fetched (all graph) is tricky. There are other methods. The problem is How deep the validation will run into?

If you want to validate all graph, you could implement the ValidateEntity method and in there you could walk on the related entities and call its ValidateEntity method. In that related entities you must do the same.

You could write a validator that implements IValidator that make this recursive validation. Then you could write validators for each entity you want that inherits from your new ValidatorRecursiveBase (or something like that) and make the individual validation in there.

David Elizondo | LLBLGen Support Team
Posts: 93
Joined: 13-Feb-2008
# Posted on: 23-Feb-2009 15:35:20   

That's pretty close to what I have prototyped thus far...

I created an [Aggregate] class attribute and a [CascadingValidation] property attribute. I use reflection in an extended ValidatorBase class to decide if the class participates in aggregate validation and which properties to validate. Sort of a recursion via inheritance scenario...

Seems to work pretty well so far, I'm able to roll up all the validation messages for the complete graph into the IDataError.Error property of the aggregate root. (which is really what i wanted to do without writing a bunch of null checks and if statements)

I'd be glad to post my solution if anyone is interested.

Heather
User
Posts: 17
Joined: 16-Jan-2009
# Posted on: 23-Feb-2009 16:54:50   

I would love to see your solution as I am working through the same scenario.

Heather

Posts: 93
Joined: 13-Feb-2008
# Posted on: 23-Feb-2009 18:00:22   

Remember this is in prototype quality right now. But the general idea is there. IValidateable exists to validate classes other than llblgen entities. This way you can decorate custom properties as [CascadingValidation] and use the llblgen validation framework for custom classes and generated entities...

All comments and suggestions are welcome!


public abstract class AggregateValidatorBase: ValidatorBase
{
     public override void ValidateEntity(IEntityCore involvedEntity)
     {
          StringBuilder sb = new StringBuilder();

        
         Validate(involvedEntity, sb);
          ValidateAssociatedEntities(involvedEntity, sb);

          if (sb.Length > 0)
                {
                    string error = sb.ToString();
                    involvedEntity.SetEntityError(error);
                    throw new ORMEntityValidationException(error, involvedEntity);
                }
     }

     //Do regular validation on the entity
     //Append validation messages to the stringbuilder
    protected abstract void Validate(IEntityCore involvedEntity, StringBuilder sb);

     private void ValidateAssociatedEntities(IEntityCore involvedEntity, StringBuilder sb)
     {
           Type type = involvedEntity.GetType();            
            PropertyInfo[] properties; 
            object[] atts;
            IValidateable associatedEntity;
            IEnumerable associatedCollection;
            object propertyValue;

            atts = type.GetCustomAttributes(typeof(AggregateAttribute), true);

            if (atts != null && atts.Length > 0)
            {
                properties = type.GetProperties();

                if (properties != null && properties.Length > 0)
                {
                    foreach (PropertyInfo pi in properties)
                    {
                        atts = pi.GetCustomAttributes(typeof(CascadingValidationAttribute), true);

                        if (atts != null && atts.Length > 0)
                        {
                            propertyValue = pi.GetValue(involvedEntity, null);
                            if ((associatedEntity = propertyValue as IValidateable) != null)
                            {
                                ValidateAssociatedEntity(associatedEntity, sb);
                            }
                            else if ((associatedCollection = propertyValue as IEnumerable) != null)
                            {
                                foreach (IValidateable collectionItem in associatedCollection)
                                {
                                    if (collectionItem != null)
                                    {
                                        ValidateAssociatedEntity(collectionItem, sb);
                                    }
                                }
                            }
                        }
                    }
                }
            }
     }

     private void ValidateAssociatedEntity(IValidateable associatedEntity, StringBuilder sb)
        {
            try
            {
                associatedEntity.ValidateEntity();
            }
            catch (ORMEntityValidationException e)
            {
                sb.Append(e.Message);
            }
        }
}

    [AttributeUsage(AttributeTargets.Class)]
    internal class AggregateAttribute:Attribute
    {
    }

    [AttributeUsage(AttributeTargets.Property)]
    internal class CascadingValidationAttribute : Attribute
    {

    }

    internal interface IValidateable
    {
        void ValidateEntity();
    }

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 24-Feb-2009 08:47:39   

Thanks for the contibution.