2008-09-05 19 views
11

Si tengo una colección de tablas de base de datos (en un archivo de Access, por ejemplo) y necesito validar cada tabla en esta colección contra un conjunto de reglas que tienen reglas comunes en todas las tablas y reglas individuales específico para uno o un subconjunto de tablas, ¿alguien puede recomendar un buen patrón de diseño para estudiar?Patrones de diseño de validación de datos

En concreto, me gustaría evitar código similar a:

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 
} 

Además, he decidido utilizar log4net para registrar todos los errores y advertencias, por lo que cada método puede ser declarado void y doesn No es necesario que devuelva nada. ¿Es esta una buena idea o sería mejor crear algún tipo de ValidationException que capte todas las excepciones y las almacene en un List<ValidationException> antes de imprimirlas todas al final?

Encontré this, que parece que puede funcionar, pero espero encontrar algunas muestras de código para trabajar. ¿Alguna sugerencia? ¿Alguien ha hecho algo similar en el pasado?

Para algunos antecedentes, el programa se escribirá en C# o VB.NET y las tablas estarán más que probablemente almacenadas en Access o SQL Server CE.

Respuesta

13

Solo una actualización sobre esto: decidí irme con el Decorator pattern. Es decir, tengo una clase de tabla 'genérica' que implementa una interfaz IValidateableTable (que contiene el método validate()). Luego, creé varios decoradores de validación (que también son implement IValidateableTable) que puedo envolver alrededor de cada tabla que intento validar.

Por lo tanto, el código termina pareciéndose a esto:

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

Entonces, todo lo que necesita hacer es llamar table1.Validate() que se desenrolla a través de los decoradores llamando a todos las validaciones necesarias. Hasta ahora, parece funcionar muy bien, aunque todavía estoy abierto a sugerencias.

5

Devolvería algún tipo de ValidationSummary para cada uno ... o un IList según cómo quiera estructurarlo.

también se puede optar por hacer un poco de magia de esta manera:

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

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

    DoSomethingElse(); 
} 

entonces el ValidateTable sería simplemente meter la mano en el ámbito actual, así:

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

algo por el estilo.

+0

Los patrones de diseño son muy agradables, pero a menudo, las soluciones simples como ésta es más que aceptable para resolver el problema. – Samuel

+0

Curioso, ¿este patrón de diseño tiene un nombre bien conocido? – Phil

4

dos enfoques:

  1. CSLA donde se utilizan métodos anónimos en los objetos de negocio para su validación.
  2. Lea el blog JP Boodhoo's donde ha implementado un motor de reglas y ha publicado publicaciones detalladas y ejemplos de código. También puede verlo en el trabajo en el episodio DNR Tv que vale la pena ver.
1

Creo que realmente está hablando de un concepto llamado constraints en el mundo de las bases de datos. Las restricciones son cómo una base de datos garantiza la integridad de los datos que contiene.Tiene mucho más sentido colocar este tipo de lógica en la base de datos, en lugar de la aplicación (incluso Access ofrece formas rudimentarias de restricciones, como requerir la unicidad de los valores en una columna, o valores de una lista, etc.).
La validación de entrada (de campos individuales) es por supuesto una cuestión diferente, y cualquier aplicación debería realizar eso (para proporcionar comentarios agradables al usuario en caso de problemas), incluso si la DB tiene restricciones bien definidas de las columnas de la tabla .

0

me gustaría probar con una combinación de los patrones de fábrica y de los visitantes:

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); 
     } 
    } 
}