Frage

Ich arbeite an einer Anwendung, mit der Zahnärzte Informationen über bestimmte klinische Aktivitäten erfassen können. Obwohl die Anwendung nicht sehr anpassbar ist (keine benutzerdefinierten Workflows oder Formulare), bietet sie einige rudimentäre Anpassungsfunktionen. Clients können die vordefinierten Formularfelder mit ihren eigenen individuellen Feldern erweitern. Es gibt ungefähr ein halbes Dutzend verschiedene Feldtypen, die Administratoren erstellen können (dh Text, Datum, numerisch, Dropdown usw.). Wir verwenden Entity-Attribut-Wert (EAV) auf der Persistenzseite, um diese Funktionalität zu modellieren.

Eine der anderen Schlüsselmerkmale der Anwendung ist die Möglichkeit, benutzerdefinierte Abfragen gegen diese benutzerdefinierten Felder zu erstellen. Dies wird über eine Benutzeroberfläche erreicht, in der eine beliebige Anzahl von Regeln (Datum <= (jetzt - 5 Tage), Text wie '444', Dropdown == 'ICU') erstellt werden kann. Alle Regeln sind zusammen, um eine Frage zu erstellen.

Die aktuelle Implementierung (die ich "geerbt") ist weder objektorientiert noch unit testbar. Im Wesentlichen gibt es eine einzelne "Gott" -Klasse, die alle unzähligen Regeltypen direkt in eine komplexe dynamische SQL -Aussage zusammenfasst (dh innere Verknüpfungen, äußere Verknüpfungen und Subelects). Dieser Ansatz ist aus mehreren Gründen problematisch:

  • Unit -Testen einzelner Regeln isoliert ist nahezu unmöglich
  • Dieser letzte Punkt bedeutet auch, dass das Hinzufügen zusätzlicher Regeltypen in der Zukunft in Zukunft definitiv gegen das offen geschlossene Prinzip verstoßen wird.
  • Geschäftslogik und Beharrlichkeitsbedenken werden gemeinsam.
  • Langsame laufende Unit-Tests Da eine echte Datenbank erforderlich ist (SQLLITE kann T-SQL nicht analysieren und ein Parser verspottet, wäre uhh ... hart)

Ich versuche, ein Ersatzdesign zu entwickeln, das flexibel, wartbar und überprüfbar ist und gleichzeitig die Abfrageleistung ziemlich bissig hält. Dieser letzte Punkt ist der Schlüssel, da ich mir vorstellen kann, dass eine OOAD -basierte Implementierung zumindest einige der Datenfilterlogik vom Datenbankserver auf den (.NET-) Anwendungsserver verschiebt.

Ich denke über eine Kombination aus dem Befehl und den Verantwortungsketten nach:

Die Abfrageklasse enthält eine Sammlung abstrakter Regelklassen (Daterule, Textrule usw.). und hält einen Verweis auf eine Datensatzklasse, die einen nicht filterierten Datensatz enthält. Der Datensatz ist in einer Agnostischen Persistenz modelliert (dh keine Referenzen oder Haken in Datenbanktypen)

Regel hat eine einzelne Filter () -Methode, die einen Datensatz annimmt, sie angemessen filtert und dann an den Anrufer zurückgibt. Die Abfrageklasse ist einfach über jede Regel iteriert, sodass jeder Regel den Datensatz filtern, sobald er passt. Die Ausführung würde aufhören, sobald alle Regeln ausgeführt wurden oder sobald der Datensatz auf nichts gefiltert wurde.

Das einzige, was mich an diesem Ansatz beunruhigt, sind die Auswirkungen auf die Leistung eines potenziell großen, nicht veriltenden Datensatzes in .NET. Sicherlich gibt es einige bewährte Ansätze zur Lösung dieser Art von Problem, die ein gutes Gleichgewicht zwischen Wartbarkeit und Leistung bieten?

Ein letzter Hinweis: Das Management erlaubt nicht die Verwendung von NHiNRNATE. LINQ zu SQL mag möglich sein, aber ich bin mir nicht sicher, wie anwendbar diese Technologie für die jeweilige Aufgabe sein würde.

Vielen Dank und ich freue mich auf das Feedback aller!

UPDATE: Suchen Sie immer noch nach einer Lösung.

War es hilfreich?

Lösung

Ich denke, dass LINQ zu SQL eine ideale Lösung ist, die möglicherweise mit dynamischem Linq aus den VS2008 -Proben gekoppelt ist. Mit LINQ, insbesondere mit Erweiterungsmethoden auf IEnumerable/iQueryable, können Sie Ihre Abfragen mit Ihrer Standard- und benutzerdefinierten Logik abhängig von den von Ihnen erhaltenen Eingaben aufbauen. Ich verwende diese Technik stark, um Filter für viele meiner MVC -Aktionen zu einem großen Effekt zu implementieren. Da es tatsächlich einen Ausdrucksbaum erstellt, wird er an dem Punkt, an dem die Abfrage geliefert werden muss, die SQL generiert, ich denke, sie wäre ideal für Ihr Szenario, da der größte Teil des schweren Hebens vom SQL -Server noch durchgeführt wird. In Fällen, in denen sich LINQ nachweist, dass sie nicht optimale Abfragen generieren, können Sie jederzeit mit Tabellenwertfunktionen oder gespeicherten Verfahren, die Ihrem LINQ-Datenkontext hinzugefügt wurden, als Methoden verwendet, um optimierte Abfragen zu nutzen.

Aktualisiert: Sie können auch versuchen, es zu verwenden PredicateBuilder Von C# 3.0 Kurz gesagt.

Beispiel: Finden Sie alle Bücher, in denen der Titel einen von Suchbegriffen enthält und der Verlag O'Reilly ist.

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

Andere Tipps

The way I've seen it done is by creating objects that model each of the conditions you want the user to build their query from, and build up a tree of objects using those.

From the tree of objects you should be able to recursively build up an SQL statement that satisfies the query.

The basic ones you'll need will be AND and OR objects, as well as objects to model comparison, like EQUALS, LESSTHAN etc. You'll probably want to use an interface for these objects to make chaining them together in different ways easier.

A trivial example:

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

Implementing it this way should allow you to Unit Test the rules pretty easily.

On the negative side, this solution still leaves the database to do a lot of the work, which it sounds like you don't really want to do.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top