Ja, das feste Codieren von SQL-Zeichenfolgen in Anwendungscode ist im Allgemeinen ein Anti-Pattern.
Lassen Sie uns versuchen, die Toleranz, die wir aus jahrelanger Sicht im Produktionscode entwickelt haben, aufzuheben. Das Mischen völlig unterschiedlicher Sprachen mit unterschiedlicher Syntax in derselben Datei ist im Allgemeinen keine wünschenswerte Entwicklungstechnik. Dies unterscheidet sich von Vorlagensprachen wie Razor, die so konzipiert sind, dass sie mehreren Sprachen eine kontextbezogene Bedeutung verleihen. Wie Sava B. in einem Kommentar unten erwähnt, ist SQL in C # oder einer anderen Anwendungssprache (Python, C ++ usw.) eine Zeichenfolge wie jede andere und semantisch bedeutungslos. Dasselbe gilt in den meisten Fällen für das Mischen mehrerer Sprachen, obwohl dies offensichtlich in bestimmten Situationen akzeptabel ist, z. B. Inline-Assemblierung in C, kleine und verständliche CSS-Ausschnitte in HTML (wobei darauf hingewiesen wird, dass CSS für das Mischen mit HTML ausgelegt ist) ), und andere.
(Robert C. Martin über das Mischen von Sprachen, Clean Code , Kapitel 17, "Code Smells and Heuristics", Seite 288)
Für diese Antwort werde ich mich auf SQL konzentrieren (wie in der Frage gestellt). Die folgenden Probleme können auftreten, wenn SQL als ein Satz nicht zugeordneter Zeichenfolgen à la carte gespeichert wird:
- Die Datenbanklogik ist schwer zu finden. Wonach suchen Sie, um alle Ihre SQL-Anweisungen zu finden? Zeichenfolgen mit "SELECT", "UPDATE", "MERGE" usw.?
- Das Refactoring von Anwendungen mit demselben oder ähnlichem SQL wird schwierig.
- Das Hinzufügen von Unterstützung für andere Datenbanken ist schwierig. Wie würde man das erreichen? Fügen Sie if..then-Anweisungen für jede Datenbank hinzu und speichern Sie alle Abfragen als Zeichenfolgen in der Methode.
- Entwickler lesen eine Anweisung in einer anderen Sprache und werden durch die Verschiebung des Fokus vom Zweck der Methode zu den Implementierungsdetails der Methode (wie und woher Daten abgerufen werden) abgelenkt.
- Während Einzeiler kein allzu großes Problem darstellen, fallen Inline-SQL-Zeichenfolgen auseinander, wenn Anweisungen komplexer werden. Was machen Sie mit einer 113-Zeilen-Anweisung? Setzen Sie alle 113 Zeilen in Ihre Methode ein?
- Wie verschiebt der Entwickler Abfragen effizient zwischen seinem SQL-Editor (SSMS, SQL Developer usw.) und seinem Quellcode hin und her? Das
@
Präfix von C # erleichtert dies, aber ich habe viel Code gesehen, der jede SQL-Zeile in Anführungszeichen setzt und die Zeilenumbrüche übergeht.
"SELECT col1, col2...colN"\
"FROM painfulExample"\
"WHERE maintainability IS NULL"\
"AND modification.effort > @necessary"\
- Einrückungszeichen, die zum Ausrichten der SQL an den umgebenden Anwendungscode verwendet werden, werden bei jeder Ausführung über das Netzwerk übertragen. Dies ist für kleine Anwendungen wahrscheinlich unbedeutend, kann sich jedoch mit zunehmender Nutzung der Software summieren.
Vollständige ORMs (Object-Relational Mappers wie Entity Framework oder Hibernate) können zufällig gepfeffertes SQL im Anwendungscode eliminieren. Meine Verwendung von SQL- und Ressourcendateien ist nur ein Beispiel. ORMs, Hilfsklassen usw. können dazu beitragen, das Ziel eines saubereren Codes zu erreichen.
Wie Kevin in einer früheren Antwort sagte, kann SQL in Code in kleinen Projekten akzeptabel sein, aber große Projekte beginnen als kleine Projekte, und die Wahrscheinlichkeit, dass die meisten Teams zurückkehren und es richtig machen, ist oft umgekehrt proportional zur Codegröße.
Es gibt viele einfache Möglichkeiten, SQL in einem Projekt beizubehalten. Eine der Methoden, die ich häufig verwende, besteht darin, jede SQL-Anweisung in eine Visual Studio-Ressourcendatei mit dem Namen "sql" zu schreiben. Eine Textdatei, ein JSON-Dokument oder eine andere Datenquelle kann abhängig von Ihren Tools sinnvoll sein. In einigen Fällen ist eine separate Klasse für die Erstellung von SQL-Zeichenfolgen möglicherweise die beste Option, kann jedoch einige der oben beschriebenen Probleme aufweisen.
SQL-Beispiel: Was sieht eleganter aus ?:
using(DbConnection connection = Database.SystemConnection()) {
var eyesoreSql = @"
SELECT
Viewable.ViewId,
Viewable.HelpText,
PageSize.Width,
PageSize.Height,
Layout.CSSClass,
PaginationType.GroupingText
FROM Viewable
LEFT JOIN PageSize
ON PageSize.Id = Viewable.PageSizeId
LEFT JOIN Layout
ON Layout.Id = Viewable.LayoutId
LEFT JOIN Theme
ON Theme.Id = Viewable.ThemeId
LEFT JOIN PaginationType
ON PaginationType.Id = Viewable.PaginationTypeId
LEFT JOIN PaginationMenu
ON PaginationMenu.Id = Viewable.PaginationMenuId
WHERE Viewable.Id = @Id
";
var results = connection.Query<int>(eyesoreSql, new { Id });
}
Wird
using(DbConnection connection = Database.SystemConnection()) {
var results = connection.Query<int>(sql.GetViewable, new { Id });
}
Der SQL-Code befindet sich immer in einer leicht zu lokalisierenden Datei oder in einer Gruppe von Dateien, von denen jede einen beschreibenden Namen hat, der beschreibt, was sie tun, anstatt wie sie es tun :
Diese einfache Methode führt eine Einzelabfrage aus. Meiner Erfahrung nach steigt der Nutzen, wenn die Verwendung der "Fremdsprache" komplexer wird.
Meine Verwendung einer Ressourcendatei ist nur ein Beispiel. Je nach Sprache (in diesem Fall SQL) und Plattform können unterschiedliche Methoden geeigneter sein.
Diese und andere Methoden lösen die obige Liste folgendermaßen auf:
- Der Datenbankcode ist leicht zu finden, da er bereits zentralisiert ist. In größeren Projekten gruppieren Sie like-SQL in separaten Dateien, möglicherweise unter einem Ordner mit dem Namen
SQL
.
- Die Unterstützung für eine zweite, dritte usw. Datenbank ist einfacher. Erstellen Sie eine Schnittstelle (oder eine andere Sprachabstraktion), die die eindeutigen Anweisungen jeder Datenbank zurückgibt. Die Implementierung für jede Datenbank besteht nur aus Anweisungen, die den folgenden ähneln:
return SqlResource.DoTheThing;
Diese Implementierungen können zwar die Ressource überspringen und die SQL in einer Zeichenfolge enthalten, es treten jedoch weiterhin einige (nicht alle) der oben genannten Probleme auf.
- Refactoring ist einfach - verwenden Sie einfach dieselbe Ressource erneut. Mit wenigen Formatanweisungen können Sie sogar häufig denselben Ressourceneintrag für verschiedene DBMS-Systeme verwenden. Ich mache das oft.
- Die Verwendung der Sekundärsprache kann beschreibende Namen verwenden, z. B.
sql.GetOrdersForAccount
anstatt stumpferSELECT ... FROM ... WHERE...
- SQL-Anweisungen werden unabhängig von ihrer Größe und Komplexität mit einer Zeile aufgerufen.
- SQL kann zwischen Datenbank-Tools wie SSMS und SQL Developer kopiert und eingefügt werden, ohne dass Änderungen oder sorgfältiges Kopieren erforderlich sind. Keine Anführungszeichen. Keine nachgestellten Backslashes. Im Fall des Visual Studio-Ressourceneditors wird durch einen Klick die SQL-Anweisung hervorgehoben. STRG + C und dann in den SQL-Editor einfügen.
Die Erstellung von SQL in einer Ressource ist schnell, sodass die Vermischung der Ressourcennutzung mit SQL-in-Code kaum einen Anreiz bietet.
Unabhängig von der gewählten Methode habe ich festgestellt, dass das Mischen von Sprachen normalerweise die Codequalität verringert. Ich hoffe, dass einige der hier beschriebenen Probleme und Lösungen Entwicklern helfen, diesen Codegeruch zu beseitigen, wenn dies angebracht ist.