Frage

Wenn ich eine Sammlung von Datenbanktabellen habe (z. B. in einer Access-Datei) und jede Tabelle in dieser Sammlung anhand eines Regelsatzes validieren muss, der sowohl gemeinsame Regeln für alle Tabellen als auch individuelle Regeln enthält, die für eine oder eine Teilmenge von spezifisch sind Tabellen, kann jemand ein gutes Designmuster empfehlen, das man sich ansehen sollte?

Insbesondere möchte ich Code wie den folgenden vermeiden:

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
}

Außerdem habe ich mich entschieden, log4net zu verwenden, um alle Fehler und Warnungen zu protokollieren, damit jede Methode deklariert werden kann void und muss nichts zurückgeben.Ist das eine gute Idee oder wäre es besser, so etwas zu erstellen? ValidationException Das fängt alle Ausnahmen ab und speichert sie in einem List<ValidationException> bevor Sie sie am Ende alle ausdrucken?

Ich habe es gefunden Das, was so aussieht, als würde es funktionieren, aber ich hoffe, tatsächlich einige Codebeispiele zu finden, an denen ich arbeiten kann.Irgendwelche Vorschläge?Hat jemand in der Vergangenheit etwas Ähnliches gemacht?

Für einige Hintergrundinformationen wird das Programm entweder in C# oder VB.NET geschrieben und die Tabellen werden höchstwahrscheinlich entweder in Access oder SQL Server CE gespeichert.

War es hilfreich?

Lösung

Nur ein Update dazu:Ich habe mich für das entschieden Dekorationsmuster.Das heißt, ich habe eine „generische“ Tabellenklasse, die eine implementiert IValidateableTable Schnittstelle (die enthält validate() Methode).Dann habe ich mehrere Validierungsdekoratoren erstellt (das auch implement IValidateableTable), die ich um jede Tabelle wickeln kann, die ich validieren möchte.

Der Code sieht also am Ende so aus:

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

Dann muss ich nur noch anrufen table1.Validate() Dies läuft über die Dekorateure ab, die alle erforderlichen Validierungen aufrufen.Bisher scheint es wirklich gut zu funktionieren, obwohl ich immer noch offen für Vorschläge bin.

Andere Tipps

Ich würde für jeden eine Art ValidationSummary zurückgeben ...oder eine IList, je nachdem, wie Sie sie strukturieren möchten.

Sie könnten sich auch dafür entscheiden, etwas wie dieses zu zaubern:

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

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

   DoSomethingElse();
}

dann würde die ValidateTable einfach in den aktuellen Bereich reichen, etwa so:

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

etwas in diesem Sinne.

Zwei Ansätze:

  1. CSLA Dabei werden anonyme Methoden für Geschäftsobjekte zur Validierung verwendet.
  2. Lesen JP Boodhoos Blog, in dem er eine Regel-Engine implementiert und sehr detaillierte Beiträge und Beispielcode veröffentlicht hat.Sie können ihm auch bei der Arbeit zusehen DNR-Fernseher Folge, die es wert ist, gesehen zu werden.

Ich denke, Sie sprechen wirklich von einem Konzept namens Einschränkungen in der Welt der Datenbanken.Mithilfe von Einschränkungen garantiert eine Datenbank die Integrität der darin enthaltenen Daten.Es ist viel sinnvoller, diese Art von Logik in die Datenbank zu integrieren statt in die Anwendung (selbst Access bietet rudimentäre Formen von Einschränkungen, z. B. die Anforderung der Eindeutigkeit von Werten in einer Spalte oder von Werten aus einer Liste usw.).
Die Eingabevalidierung (einzelner Felder) ist natürlich eine andere Sache, und jede Anwendung sollte dies trotzdem durchführen (um dem Benutzer im Falle von Problemen ein nettes Feedback zu geben), selbst wenn die Datenbank klar definierte Einschränkungen der Tabellenspalten hat.

Ich würde es mit einer Kombination aus den Factory- und Visitor-Mustern versuchen:

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);
        }
    }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top