Complex object model validation of different types <T>

Aug 22, 2013 at 9:18 PM
I'm using entity framework 5 with a complex model. I'm having issues with conditional requirements:

Data Model:
        public class Widget
        {
            public ICollection<Purpose> Purposes {get;set;}
            public ICollection<Bar> Bars { get; set; }
        }
        public class Purpose
        {
            public Widget Widget {get;set;}
            public int PurposeTypeID {get;set;}
        }
        public class Bar
        {
            public Widget Widget {get;set;}
            public int ID {get;set;}
            public Nullable<DateTime> Year { get; set; }
            public Nullable<int> Amount { get; set; }
        }
Filled Example Model:
            Widget widget = new Widget()
            {
                Purposes = new List<Purpose>()
                {
                     new Purpose()
                     {
                         PurposeTypeID = 1,
                         Widget = new Widget()
                         {
                             Bars = new List<Bar>()
                             {
                                new Bar()
                                {
                                    ID = 1
                                }
                             }
                         }
                     }
                }
            };
Rules Engine Code:
            FluentBuilder builder = new FluentBuilder();
            FluentBuilder BarsBuilder12 = new FluentBuilder();
            FluentBuilder BarsBuilder3 = new FluentBuilder();

            BarsBuilder12.For<Bar>()
                .Setup(b => b.Amount)
                    .WithMessage("Amount cannot be null")
                    .MustNotBeNull();

            BarsBuilder3.For<Bar>()
                .Setup(b => b.Year)
                    .WithMessage("Year cannot be null")
                    .MustNotBeNull();

            builder.For<Widget>()
                .Setup(l => l.Purposes)
                    .WithMessage("Widget must have a Purpose")
                    .MustNotBeNull()
                    .MustContainElements()  // this is my own
                    .CallValidateForEachElement();

            builder.For<Purpose>()
                .If(lp => lp.PurposeTypeID == 1 || lp.PurposeTypeID == 2)
                    .Setup(lp => lp.Widget.Bars)
                        .CallValidateForEachElement(BarsBuilder12.Build())
                .Else()
                .If(lp => lp.PurposeTypeID == 3)
                    .Setup(lp => lp.Widget.Bars)
                        .CallValidateForEachElement(BarsBuilder3.Build())
                .EndIf();

            var report = new ValidationReport(builder.Build());

            var IsValid = report.Validate(loan);

            var errors = report.GetErrorMessages()
As you can see, I'd like conditional rules on a child property of Widget based on values in another child property. Is this possible to do within this fluent engine or do I need to manually code this??

What's really happening here is because I have to define both Bars Builders the rules are passed into the engine, so when engine is validated it evaluates BOTH Bars rules instead of just the one rule I want

What I would REALLY love to see:
            builder.For<Purpose>()
                .If(lp => lp.PurposeTypeID == 1 || lp.PurposeTypeID == 2)
                    .Setup(lp => lp.Widget.Bars)
                        .CallValidateForEachElement
                        (
                            new FluentBuilder()
                                .For<Bar>()
                                .Setup(b => b.Amount)
                                    .WithMessage("Amount cannot be null")
                                    .MustNotBeNull()
                                .EndSetup()
                            .Build()
                        )
                .Else()
                .If(lp => lp.PurposeTypeID == 3)
                    .Setup(lp => lp.Widget.Bars)
                        .CallValidateForEachElement
                        (
                            new FluentBuilder()
                                .For<Bar>()
                                .Setup(b => b.Year)
                                    .WithMessage("Year cannot be null")
                                    .MustNotBeNull()
                                .EndSetup()
                            .Build()
                        )
                .EndIf();
The only issue here is .Build() is not an available fluent member after .EndSetup().

Any ideas on how to do this?

Thanks!
Coordinator
Aug 22, 2013 at 11:04 PM
Hi,

First, this code-base is not yet released.... so there may be some bugs here and there... and, I am in the process of moving all the RulesEngine code to another project and call it something more appropriate since this is in-fact a validation engine. And do some clean up with the Validation Reports. Nevertheless, here's how the Builders are meant to work:
  1. Create a builder
    1.1. Define Rules on the builder
  2. Call build on the Builder
  3. Keep and reuse the output of 2.
So. I would not do this:
var report = new ValidationReport(builder.Build());
I would do this instead:
var engine = builder.Build();
var report = new ValidationReport(engine);
//NOTE: I would keep engine in a Service Locator Factory as a singleton, or in a static variable - Assume that Building an engine is expensive.
When you say:
What's really happening here is because I have to define both Bars Builders the rules are passed into the engine, so when engine is validated it evaluates BOTH Bars rules instead of just the one rule I want
I haven't found that, depending on the condition, either rule will be called, not both. With the code you supplied, the error count is 1 - if both rules were invoked, you would have an error count of 2.

I have no intention of adding a .Build() method as part of the fluent language at this stage.... feel free to suggest it as a feature. I personally think that the first code sample you provided is more readeable than the second anyway.