Question

Je travaille sur une application qui permet aux dentistes de capturer des informations sur certaines activités cliniques. Bien que l'application ne soit pas hautement personnalisable (pas de flux de travail ni de formulaires personnalisés), elle offre des fonctionnalités de personnalisation rudimentaires. les clients peuvent choisir d’augmenter les champs de formulaire prédéfinis avec leurs propres champs personnalisés. Il existe environ une demi-douzaine de types de champs différents que les administrateurs peuvent créer (par exemple, Texte, Date, Numérique, DropDown, etc.). Nous utilisons EAV (Entity-Attribute-Value) du côté de la persistance pour modéliser cette fonctionnalité.

L’une des autres fonctionnalités clés de l’application est la possibilité de créer des requêtes personnalisées sur ces champs personnalisés. Ceci est réalisé via une interface utilisateur dans laquelle un nombre quelconque de règles (Date & Lt; = (maintenant - 5 jours), un texte tel que '444', DropDown == 'ICU') peuvent être créés. Toutes les règles sont combinées en ET pour produire une requête.

L'implémentation actuelle (dont je & "; hérité &";) n'est ni orientée objet ni testable d'unité. Il existe essentiellement un seul & Quot; Dieu & Quot; classe qui compile tous les types de règle innombrables directement dans une instruction SQL dynamique complexe (c'est-à-dire des jointures internes, des jointures externes et des sous-sélections). Cette approche est gênante pour plusieurs raisons:

  • L'unité teste des règles individuelles isolément est presque impossible
  • Ce dernier point signifie également l’ajout de types de règles supplémentaires dans l'avenir sera très certainement violer principe ouvert fermé.
  • Les problèmes de logique commerciale et de persistance se confondent.
  • Tests unitaires lents dans la mesure où une base de données réelle est requise (SQLLite ne peut pas analyser T-SQL et simuler un analyseur syntaxique serait euhh ... difficile]

J'essaie de concevoir un modèle de remplacement flexible, maintenable et testable, tout en maintenant les performances des requêtes assez rapides. Ce dernier point est essentiel car je suppose qu’une implémentation basée sur OOAD déplacera au moins une partie de la logique de filtrage des données du serveur de base de données vers le serveur d’application (.NET).

J'envisage une combinaison des modèles de commandement et de chaîne de responsabilité:

La classe Query contient une collection de classes de règles abstraites (DateRule, TextRule, etc.). et contient une référence à une classe DataSet qui contient un ensemble de données non filtré. DataSet est modélisé de manière agnostique de persistance (c.-à-d. Aucune référence ni aucun lien avec les types de base de données)

Rule a une seule méthode Filter () qui intègre un DataSet, le filtre de manière appropriée, puis le renvoie à l'appelant. La classe Query se contente d'itérer chaque règle, permettant à chaque règle de filtrer le DataSet comme bon lui semble. L'exécution s'arrête une fois toutes les règles exécutées ou le DataSet filtré à néant.

Ce qui me préoccupe dans cette approche concerne les conséquences en termes de performances de l'analyse d'un jeu de données potentiellement non filtré volumineux dans .NET. Il existe sûrement des approches éprouvées pour résoudre ce type de problème qui offrent un bon équilibre entre maintenabilité et performance.

Une dernière remarque: la direction ne permettra pas l’utilisation de NHibernate. Linq to SQL est peut-être possible, mais je ne sais pas dans quelle mesure cette technologie s’appliquera à la tâche à accomplir.

Merci beaucoup et j'attends avec impatience les commentaires de tous!

Mise à jour: Toujours à la recherche d’une solution à ce problème.

Était-ce utile?

La solution

Je pense que LINQ to SQL serait une solution idéale couplée, peut-être, avec Dynamic LINQ à partir des exemples VS2008. À l'aide de LINQ, en particulier avec les méthodes d'extension sur IEnumerable / IQueryable, vous pouvez créer vos requêtes à l'aide de votre logique standard et personnalisée en fonction des entrées que vous obtenez. J'utilise énormément cette technique pour implémenter des filtres sur bon nombre de mes actions MVC avec un grand effet. Puisqu'il construit réellement un arbre d'expression, qui l'utilise ensuite pour générer le code SQL au moment où la requête doit être matérialisée, je pense que ce serait idéal pour votre scénario car la plupart des tâches lourdes sont effectuées par le serveur SQL. Dans les cas où LINQ réussit à générer des requêtes non optimales, vous pouvez toujours utiliser des fonctions à valeur de table ou des procédures stockées ajoutées à votre contexte de données LINQ comme méthodes pour tirer parti des requêtes optimisées.

Mise à jour : vous pouvez également essayer d'utiliser PredicateBuilder . à partir de 3,0 en bref, en bref.

Exemple: recherchez tous les livres dont le titre contient l'un des termes de recherche et l'éditeur est 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 );

Autres conseils

La façon dont je l'ai fait consiste à créer des objets qui modélisent chacune des conditions à partir desquelles l'utilisateur doit créer sa requête et créer une arborescence d'objets à l'aide de celles-ci.

À partir de l'arborescence des objets, vous devriez pouvoir construire de manière récursive une instruction SQL qui satisfasse la requête.

Les objets de base dont vous aurez besoin seront les objets AND et OR, ainsi que les objets de comparaison de modèles, tels que EQUALS, LESSTHAN, etc. Vous voudrez probablement utiliser une interface pour ces objets afin de les chaîner ensemble de manière différente. manières plus faciles.

Un exemple 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();
    }
}

Cette mise en œuvre devrait vous permettre de tester assez facilement les règles.

Du côté négatif, cette solution laisse encore la base de données pour effectuer une grande partie du travail, ce qui semble ne pas vouloir vraiment.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top