Шаблоны проектирования валидации данных
Если у меня есть набор таблиц базы данных (например, в файле 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>
, прежде чем распечатывать их в конце?
Я нашел этот, который выглядит так, как будто он может работать, но я надеюсь на самом деле найти образцы кода для работы. Какие-либо предложения? Кто-нибудь сделал что-то подобное в прошлом?
Для некоторого фона программа будет записана либо на С#, либо на VB.NET, и таблицы будут, скорее всего, сохранены в Access или SQL Server CE.
Ответы
Ответ 1
Просто обновите это: я решил пойти с Decorator pattern. То есть у меня есть один "общий" класс таблицы, который реализует интерфейс IValidateableTable (который содержит метод Validate()
). Затем я создал несколько мастеров проверки (которые также реализуют IValidateableTable), которые я могу обернуть вокруг каждой таблицы, которую я пытаюсь проверки.
Итак, код выглядит следующим образом:
IValidateableTable table1 = new GenericTable(myDataSet);
table1 = new NonNullNonEmptyColumnValidator(table1, "ColumnA");
table1 = new ColumnValueValidator(table1, "ColumnB", "ExpectedValue");
Тогда все, что мне нужно сделать, это позвонить table1.Validate()
, который разворачивает через декораторов, вызывающих все необходимые проверки. Пока это работает очень хорошо, хотя я по-прежнему открыт для предложений.
Ответ 2
Я бы вернул некоторый тип 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");
что-то в этом роде.
Ответ 3
Два подхода:
- CSLA, где для проверки используются анонимные методы для бизнес-объектов.
- Прочитайте блог JP Boodhoo, где он внедрил механизм правил и опубликовал очень подробные сообщения и образец кода. Вы также можете увидеть его на работе над эпизодом DNR Tv, который стоит посмотреть.
Ответ 4
Я думаю, вы действительно говорите о концепции constraints в мире баз данных. Ограничения - это то, как база данных гарантирует целостность содержащихся в ней данных. Разумеется, это гораздо логичнее использовать эту логику в базе данных, а не в приложении (даже Access предлагает рудиментарные формы ограничений, такие как требование уникальности значений в столбце или значения из списка и т.д.).
Валидация ввода (отдельных полей) - это, конечно, другое дело, и любое приложение должно выполнить это (чтобы обеспечить хорошую обратную связь с пользователем в случае возникновения проблем), даже если у БД есть четко определенные ограничения столбцов таблицы.
Ответ 5
Я бы попытался с комбинацией шаблонов Factory и посетителя:
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);
}
}
}