Question

Si j'ai une collection de tables de base de données (dans un fichier Access, par exemple) et que je dois valider chaque table de cette collection par rapport à un ensemble de règles qui a à la fois des règles communes à toutes les tables ainsi que des règles individuelles spécifiques à un ou un sous-ensemble de tables, quelqu'un peut-il recommander un bon modèle de conception à examiner ?

Plus précisément, je voudrais éviter le code similaire à :

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
}

De plus, j'ai décidé d'utiliser log4net pour enregistrer toutes les erreurs et avertissements, afin que chaque méthode puisse être déclarée. void et n'a rien à retourner.Est-ce une bonne idée ou serait-il préférable de créer une sorte de ValidationException qui intercepte toutes les exceptions et les stocke dans un List<ValidationException> avant de tous les imprimer à la fin ?

j'ai trouvé ce, ce qui semble fonctionner, mais j'espère trouver des exemples de code sur lesquels travailler.Aucune suggestion?Quelqu'un a-t-il fait quelque chose de similaire dans le passé ?

Pour un peu de contexte, le programme sera écrit en C# ou en VB.NET et les tables seront très probablement stockées dans Access ou SQL Server CE.

Était-ce utile?

La solution

Juste une mise à jour à ce sujet:J'ai décidé d'y aller avec le Modèle de décorateur.Autrement dit, j'ai une classe de table « générique » qui implémente un IValidateableTable interface (qui contient validate() méthode).Ensuite, j'ai créé plusieurs décorateurs de validation (qui aussi implement IValidateableTable) que je peux enrouler autour de chaque table que j'essaie de valider.

Ainsi, le code finit par ressembler à ceci :

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

Ensuite, tout ce que j'ai à faire c'est d'appeler table1.Validate() qui se déroule à travers les décorateurs appelant toutes les validations nécessaires.Jusqu'à présent, cela semble très bien fonctionner, même si je suis toujours ouvert aux suggestions.

Autres conseils

Je retournerais un type de ValidationSummary pour chacun...ou un IList selon la façon dont vous souhaitez le structurer.

vous pouvez également choisir de faire de la magie comme ceci :

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

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

   DoSomethingElse();
}

alors ValidateTable atteindrait simplement la portée actuelle, comme ceci :

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

quelque chose dans ce sens.

Deux approches :

  1. AAPC où des méthodes anonymes sur les objets métier sont utilisées pour la validation.
  2. Lire JP Boodhoo's blog où il a implémenté un moteur de règles et a publié des articles très détaillés et des exemples de code.Vous pouvez également le voir à l'œuvre sur Télévision DNR épisode qui vaut vraiment la peine d'être vu.

Je pense que vous parlez vraiment d'un concept appelé contraintes dans le monde des bases de données.Les contraintes sont la manière dont une base de données garantit l'intégrité des données qu'elle contient.Il est beaucoup plus logique de mettre ce type de logique dans la base de données plutôt que dans l'application (même Access propose des formes rudimentaires de contraintes, comme exiger l'unicité des valeurs dans une colonne ou des valeurs d'une liste, etc.).
La validation des entrées (des champs individuels) est bien sûr une affaire différente, et toute application doit toujours l'effectuer (pour fournir un retour d'information intéressant à l'utilisateur en cas de problème), même si la base de données a des contraintes bien définies sur les colonnes du tableau.

J'essaierais avec une combinaison des modèles Factory et Visitor :

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);
        }
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top