Pregunta

Estoy trabajando en una aplicación que permite a los dentistas capturar información sobre ciertas actividades clínicas. Si bien la aplicación no es altamente personalizable (no hay flujos de trabajo o formularios personalizados), sí ofrece algunas capacidades de personalización rudimentarias; los clientes pueden elegir aumentar los campos de formulario predefinidos con sus propios campos personalizados. Hay alrededor de media docena de tipos de campos diferentes que los administradores pueden crear (es decir, Texto, Fecha, Numérico, Descenso, etc.). Estamos utilizando Entity-Attribute-Value (EAV) en el lado de la persistencia para modelar esta funcionalidad.

Una de las otras características clave de la aplicación es la capacidad de crear consultas personalizadas en estos campos personalizados. Esto se logra a través de una interfaz de usuario en la que se puede crear cualquier cantidad de reglas (Fecha & Lt; = (Ahora - 5 días), Texto como '444', DropDown == 'ICU'). Todas las reglas son AND 'juntas para producir una consulta.

La implementación actual (que I " heredó ") no está orientada a objetos ni a prueba de unidades. Esencialmente, hay un solo & "; Dios &"; clase que compila todos los innumerables tipos de reglas directamente en una declaración SQL dinámica compleja (es decir, uniones internas, uniones externas y subselecciones). Este enfoque es problemático por varias razones:

  • Unidad que prueba reglas individuales de forma aislada es casi imposible
  • Ese último punto también significa agregar tipos de reglas adicionales en el el futuro definitivamente violará el principio abierto cerrado.
  • La lógica de negocios y las preocupaciones de persistencia se están mezclando.
  • Pruebas unitarias de ejecución lenta ya que se requiere una base de datos real (SQLLite no puede analizar T-SQL y burlarse de un analizador sería uhh ... difícil)

Estoy tratando de llegar a un diseño de reemplazo que sea flexible, mantenible y comprobable, manteniendo el rendimiento de la consulta bastante ágil. Este último punto es clave ya que imagino que una implementación basada en OOAD moverá al menos parte de la lógica de filtrado de datos del servidor de la base de datos al servidor de aplicaciones (.NET).

Estoy considerando una combinación de los patrones de Comando y Cadena de Responsabilidad:

La clase Query contiene una colección de clases de reglas abstractas (DateRule, TextRule, etc.). y contiene una referencia a una clase DataSet que contiene un conjunto de datos sin filtrar. DataSet está modelado de manera independiente de la persistencia (es decir, sin referencias ni enlaces en los tipos de bases de datos)

La regla tiene un único método Filter () que toma un DataSet, lo filtra apropiadamente y luego lo devuelve a la persona que llama. La clase Query simplemente itera sobre cada regla, permitiendo que cada regla filtre el DataSet como mejor le parezca. La ejecución se detendría una vez que se hayan ejecutado todas las reglas o una vez que el DataSet se haya filtrado a nada.

Lo único que me preocupa de este enfoque son las implicaciones de rendimiento de analizar un conjunto de datos sin filtrar potencialmente grande en .NET. ¿Seguramente hay algunos enfoques probados y verdaderos para resolver este tipo de problema que ofrecen un buen equilibrio entre mantenibilidad y rendimiento?

Una nota final: la administración no permitirá el uso de NHibernate. Linq to SQL podría ser posible, pero no estoy seguro de cuán aplicable sería esa tecnología a la tarea en cuestión.

¡Muchas gracias y espero los comentarios de todos!

Actualización: Todavía estoy buscando una solución para esto.

¿Fue útil?

Solución

Creo que LINQ to SQL sería una solución ideal combinada, quizás, con Dynamic LINQ de las muestras VS2008. Usando LINQ, particularmente con los métodos de extensión en IEnumerable / IQueryable, puede construir sus consultas usando su lógica estándar y personalizada dependiendo de las entradas que obtenga. Utilizo esta técnica en gran medida para implementar filtros en muchas de mis acciones MVC con gran efecto. Dado que en realidad construye un árbol de expresión y luego lo usa para generar el SQL en el punto donde la consulta debe materializarse, creo que sería ideal para su escenario, ya que la mayor parte del trabajo pesado aún lo realiza el servidor SQL. En los casos en que LINQ demuestre generar consultas no óptimas, siempre puede usar funciones con valores de tabla o procedimientos almacenados agregados a su contexto de datos LINQ como métodos para aprovechar las consultas optimizadas.

Actualizado : También puede intentar usar PredicateBuilder de C # 3.0 en una cáscara de nuez.

Ejemplo: busque todos los libros donde el título contiene uno de un conjunto de términos de búsqueda y el editor es O'Reilly.

 var predicate = PredicateBuilder.True<Book>();
 predicate = predicate.And( b => b.Publisher == "O'Reilly" );
 var titlePredicate = PredicateBuilder.False<Book>();
 foreach (var term in searchTerms)
 {
     titlePredicate = titlePredicate.Or( b => b.Title.Contains( term ) );
 }
 predicate = predicate.And( titlePredicate );

 var books = dc.Book.Where( predicate );

Otros consejos

La forma en que lo he visto es creando objetos que modelen cada una de las condiciones desde las cuales desea que el usuario construya su consulta, y construya un árbol de objetos usando esas.

Desde el árbol de objetos debería poder construir recursivamente una declaración SQL que satisfaga la consulta.

Los básicos que necesitará serán objetos AND y OR, así como objetos para comparar modelos, como EQUALS, LESSTHAN, etc. Probablemente quiera usar una interfaz para estos objetos para encadenarlos en diferentes mucho más fácil.

Un ejemplo trivial:

public interface IQueryItem
{
    public String GenerateSQL();
}


public class AndQueryItem : IQueryItem
{
    private IQueryItem _FirstItem;
    private IQueryItem _SecondItem;

    // Properties and the like

    public String GenerateSQL()
    {
        StringBuilder builder = new StringBuilder();
        builder.Append(_FirstItem.GenerateSQL());
        builder.Append(" AND ");
        builder.Append(_SecondItem.GenerateSQL());

        return builder.ToString();
    }
}

Implementarlo de esta manera debería permitirle probar las reglas de la unidad con bastante facilidad.

En el lado negativo, esta solución todavía deja la base de datos para hacer mucho trabajo, lo que parece que realmente no quieres hacer.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top