Шаблоны проектирования валидации данных - PullRequest
12 голосов
/ 05 сентября 2008

Если у меня есть коллекция таблиц базы данных (например, в файле Access), и мне нужно проверить каждую таблицу в этой коллекции по набору правил, который имеет как общие правила для всех таблиц, так и отдельные правила, относящиеся к одной или подмножество таблиц, кто-то может порекомендовать хороший шаблон дизайна для изучения?

В частности, я хотел бы избежать кода, похожего на:

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
}

Кроме того, я решил использовать log4net для регистрации всех ошибок и предупреждений, чтобы каждый метод мог быть объявлен void и не должен ничего возвращать. Это хорошая идея, или было бы лучше создать какой-то ValidationException, который перехватит все исключения и сохранит их в List<ValidationException>, прежде чем распечатать их в конце?

Я нашел этот , который выглядит так, как будто он может работать, но я надеюсь найти примеры кода для работы. Какие-либо предложения? Кто-нибудь делал что-то подобное в прошлом?

В некоторых случаях программа будет написана на C # или VB.NET, а таблицы, скорее всего, будут храниться либо в Access, либо в SQL Server CE.

Ответы [ 5 ]

14 голосов
/ 16 сентября 2008

Просто обновление об этом: я решил пойти с шаблоном Decorator . То есть у меня есть один «универсальный» класс таблицы, который реализует интерфейс IValidateableTable (который содержит метод validate()). Затем я создал несколько декораторов проверки (это также implement IValidateableTable), которые я могу обернуть вокруг каждой таблицы, которую я пытаюсь проверить.

Итак, код выглядит примерно так:

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

Затем все, что мне нужно сделать, это вызвать table1.Validate(), который раскручивается через декораторы, вызывая все необходимые проверки. Пока что, похоже, он работает очень хорошо, хотя я все еще открыт для предложений.

5 голосов
/ 05 сентября 2008

Я бы вернул некоторый тип ValidationSummary для каждого ... или IList в зависимости от того, как вы хотите его структурировать.

Вы также можете выбрать магию, подобную этой:

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

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

   DoSomethingElse();
}

тогда ValidateTable просто достигнет текущей области, например:

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

что-то на этот счет.

4 голосов
/ 21 сентября 2008

Два подхода:

  1. CSLA , где для проверки используются анонимные методы бизнес-объектов.
  2. Читать Блог JP Boodhoo , где он внедрил механизм правил и опубликовал очень подробные публикации и примеры кода. Вы также можете увидеть его на работе в эпизоде ​​ DNR Tv , который стоит посмотреть.
1 голос
/ 16 июня 2017

Я бы попробовал с комбинацией шаблонов Фабрика и Посетитель:

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);
        }
    }
}
1 голос
/ 10 октября 2008

Я думаю, что вы действительно говорите о концепции под названием ограничений в мире баз данных. Ограничения - это то, как база данных гарантирует целостность содержащихся в ней данных. Гораздо больше смысла помещать такую ​​логику в базу данных, а не в приложение (даже Access предлагает элементарные формы ограничений, такие как требование уникальности значений в столбце или значений из списка и т. Д.).
Проверка входных данных (отдельных полей) - это, конечно, другое дело, и любое приложение все равно должно это выполнять (чтобы обеспечить хорошую обратную связь с пользователем в случае проблем), даже если в БД есть четко определенные ограничения столбцов таблицы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...