Question

If I have a collection of database tables (in an Access file, for example) and need to validate each table in this collection against a rule set that has both common rules across all tables as well as individual rules specific to one or a subset of tables, can someone recommend a good design pattern to look into?

Specifically, I would like to avoid code similar to:

void Main()
{
    ValidateTable1();
    ValidateTable2();
    ValidateTable3();
}

private void ValidateTable1()
{
    //Table1 validation code goes here
}

private void ValidateTable2()
{
    //Table2 validation code goes here
}

private void ValidateTable3()
{
    //Table3 validation code goes here
}

Also, I've decided to use log4net to log all of the errors and warnings, so that each method can be declared void and doesn't need to return anything. Is this a good idea or would it be better to create some sort of ValidationException that catches all exceptions and stores them in a List<ValidationException> before printing them all out at the end?

I did find this, which looks like it may work, but I'm hoping to actually find some code samples to work off of. Any suggestions? Has anyone done something similar in the past?

For some background, the program will be written in either C# or VB.NET and the tables will more than likely be stored in either Access or SQL Server CE.

Was it helpful?

Solution

Just an update on this: I decided to go with the Decorator pattern. That is, I have one 'generic' table class that implements an IValidateableTable interface (which contains validate() method). Then, I created several validation decorators (that also implement IValidateableTable) which I can wrap around each table that I'm trying to validate.

So, the code ends up looking like this:

IValidateableTable table1 = new GenericTable(myDataSet);
table1 = new NonNullNonEmptyColumnValidator(table1, "ColumnA");
table1 = new ColumnValueValidator(table1, "ColumnB", "ExpectedValue");

Then, all I need to do is call table1.Validate() which unwinds through the decorators calling all of the needed validations. So far, it seems to work really well, though I am still open to suggestions.

OTHER TIPS

I'd return some type of ValidationSummary for each one... or an IList depending on how you want to structure it.

you could also opt to do some magic like this:

using(var validation = new ValidationScope())
{
   ValidateTable1();
   ValidateTable2();
   ValidateTable3();

   if(validation.Haserrors)
   {
       MessageBox.Show(validation.ValidationSummary);
       return;
   }

   DoSomethingElse();
}

then the ValidateTable would just reach into the current scope, like this:

ValidationScope.Current.AddError("col1", "Col1 should not be NULL");

something to that effect.

Two approaches:

  1. CSLA where anonymous methods on business objects are used for validation.
  2. Read JP Boodhoo's blog where he has implemented a rules engine and has very detailed posts and sample code published. You can also see him at work on DNR Tv episode that's well worth watching.

I think you are really talking about a concept called constraints in the world of databases. Constraints are how a database guarantees the integrity of the data it contains. It makes much more sense to put this sort of logic in the database, rather than the application (even Access offers rudimentary forms of constraints, such as requiring uniqueness of values in a column, or values from a list, etc.).
Input validation (of individual fields) is of course a different matter, and any application should still perform that (to provide nice feedback to the user in case of problems), even if the DB has well-defined constraints of the table columns.

I would try with a combination of the Factory and Visitor patterns:

using System;
using System.Collections.Generic;

namespace Example2
{
    interface IVisitor
    {
        void Visit(Table1 table1);
        void Visit(Table2 table2);
    }

    interface IVisitable
    {
        void Accept(IVisitor visitor);
    }

    interface ILog
    {
        void Verbose(string message);
        void Debug(string messsage);
        void Info(string message);
        void Error(string message);
        void Fatal(string message);
    }

    class Error
    {
        public string Message { get; set; }
    }

    class Table1 : IVisitable
    {
        public int Id { get; set; }
        public string Data { get; set; }
        private IList<Table2> InnerElements { get; } = new List<Table2>();

        public void Accept(IVisitor visitor)
        {
            visitor.Visit(this);

            foreach(var innerElement in InnerElements)
                visitor.Visit(innerElement);
        }
    }

    class Table2 : IVisitable
    {
        public int Id { get; set; }
        public int Data { get; set; }

        public void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    class Validator : IVisitor
    {
        private readonly ILog log;
        private readonly IRuleSet<Table1> table1Rules;
        private readonly IRuleSet<Table2> table2Rules;

        public Validator(ILog log, IRuleSet<Table1> table1Rules, IRuleSet<Table2> table2Rules)
        {
            this.log = log;
            this.table1Rules = table1Rules;
            this.table2Rules = table2Rules;
        }

        public void Visit(Table1 table1)
        {
            IEnumerable<Error> errors = table1Rules.EnforceOn(table1);

            foreach (var error in errors)
                log.Error(error.Message);
        }

        public void Visit(Table2 table2)
        {
            IEnumerable<Error> errors = table2Rules.EnforceOn(table2);

            foreach (var error in errors)
                log.Error(error.Message);
        }
    }

    class RuleSets
    {
        private readonly IRuleSetFactory factory;

        public RuleSets(IRuleSetFactory factory)
        {
            this.factory = factory;
        }

        public IRuleSet<Table1> RulesForTable1 =>
            factory.For<Table1>()
                .AddRule(o => string.IsNullOrEmpty(o.Data), "Data1 is null or empty")
                .AddRule(o => o.Data.Length < 10, "Data1 is too short")
                .AddRule(o => o.Data.Length > 26, "Data1 is too long");

        public IRuleSet<Table2> RulesForTable2 =>
            factory.For<Table2>()
                .AddRule(o => o.Data < 0, "Data2 is negative")
                .AddRule(o => o.Data > 10, "Data2 is too big");
    }

    interface IRuleSetFactory
    {
        IRuleSet<T> For<T>();
    }

    interface IRuleSet<T>
    {
        IEnumerable<Error> EnforceOn(T obj);
        IRuleSet<T> AddRule(Func<T, bool> rule, string description);
    }

    class Program
    {
        void Run()
        {
            var log = new ConsoleLogger();
            var factory = new SimpleRules();
            var rules = new RuleSets(factory);
            var validator = new Validator(log, rules.RulesForTable1, rules.RulesForTable2);

            var toValidate = new List<IVisitable>();
            toValidate.Add(new Table1());
            toValidate.Add(new Table2());

            foreach (var validatable in toValidate)
                validatable.Accept(validator);
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top