| 

.NET C# Java Javascript Exception

4
Hallo zusammen,
ich habe eine Reihe im Quältext hinterlegter SQL-Statements zu pflegen. Da die teilweise recht lang sind, sind die natürlich mit Zeilenumbrüchen geschrieben, also ein Haufen konkatenierter Strings. Da habe ich ja erstmal prinzipiell 3 Möglichkeiten:
1)
string stmt = "SELECT " +
"bierflasche " +
"FROM kühlschrank " +
"WHERE temperatur < 8";

2)
StringBuilder sb = new StringBuilder();
sb.Append("SELECT ");
sb.Append("bierflasche ");
sb.Append("FROM kühlschrank ");
sb.Append("WHERE temperatur < 8");
string stmt = sb.ToString();

3)
string stmt = @"SELECT 
bierflasche
FROM kühlschrank
WHERE temperatur < 8";

1) Ist eigentlich grausam und scheidet aus.
2) Möglicherweise die performanteste Variante,
während
3) den unschlagbaren Vorteil hat, dass die Statements ohne weitere Bearbeitung aus dem Quältext ausgeschnitten werden können und bsp. in einem Administrationstool für die Datenbank direkt per Einfügen weiterverwendet werden können.
Gibt es noch weitere Vor- und Nachteile?
27.09.2011
chriscolm 126 1 3
7 Antworten
2
Ich würde folgende schreibweise verwenden:

#Region SQL-Statements
const string stmt = @"
SELECT bierflasche
FROM kühlschrank
WHERE temperatur < @temperatur
";
#end Region

//..

SqlCommand cmd= new SqlCommand(stmt, conn);
cmd.Parameters.Add(new SqlParameter("@temperatur", 8));

//...

Vorteile:

  • 1. Const-Variablen können bereits vom Compiler evaluiert werden und direkt in den IL-Code eingebettet werden

  • 2. Das "binden" von veränderlichen Statementparametern bringt dem SQL-Server Vorteile, oder besser gesagt dem Optimizier des SQL-Servers. Da jedes Statement vom SQL-Server compiliert werden muss nimmt es Platz im Procedure-Cache ein. Dieser ist dazu das das identische Statements nicht nochmal compiliert werden müssen. Wenn du Statementparameter also direkt ins Statement einbettest wird das Statement jedes mal compiliert, in den Procedurecache abgelegt und dann ausgeführt. Das "binden" sorgt dafür das dies in diesem Fall nicht passiert. (ich kann gern auf diesen Punkt näher eingehen wenn du es wünschst)

  • 3. Die Region erhöht die Übersichtlichkeit und das schnelle auffinden der Statements .. alternativ kann man aber auch Storage-Procedures verwenden, Resourcenfiles ...
27.09.2011
Floyd 14,6k 3 9
1
Am ehesten plädiere ich für:
string stmt = @"
SELECT bierflasche
FROM kühlschrank
WHERE temperatur < @temperatur
";

SqlCommand command = new SqlCommand(stmt, conn);
command.Parameters.Add(new SqlParameter("@temperatur", 8));

Spätestens wenn du auf einen OR-Mapper umstellst, erübrigt sich die Frage ;)
27.09.2011
Jürgen Luhr 7,1k 1 9
1
Variante 4:
using (var context = new KuechenEntities())
{
var bierflaschen =
from p in context.kuehlschrank
where p.temperatur < 8
orderby p.haltbarkeitsdatum
select p.bierflasche;
}
27.09.2011
Jürgen Luhr 7,1k 1 9
2
Wenn die Anwendung nicht mit NET 2.0 laufen muss und man Zeit und Lust hast, mal eben gefühlt ne halbe Million Statements umzuschreiben, ist das ne Option :-)
chriscolm 27.09.2011
Das ist ein glasklares Argument. ;o)
Jürgen Luhr 27.09.2011
1
Ich finde auch Variante 3 am besten. Alle 3 Varianten haben den kleinen Nachteil, dass Du kein(e) (SQL-)Syntax-Highlighting/-Prüfung hast. D.h. wenn Du wirklich viele und große SQL-Befehle zu pflegen hast, käme u.U. auch die Auslagerung in eigene Dateien in Betracht, die Du dann beim Initialisieren der Anwendung einliest. Aber das lohnt den Aufwand wirklich nur, wenn es sonst SEHR unhandlich wird - oder wenn Du für den Betrieb eine Aufstellung aller SQL-Befehle brauchst, die Deine Anwendung verwendet. Noch sind nicht alle Admins in Rente (und das ist kein Witz, sondern traurige Wahrheit), die auf ihren heiligen Systemen nur Anwendungen zulassen, die nur mit zur Deploy-Zeit festliegenden und vorzeigbaren SQL-Befehlen arbeiten. Aber das nur als Fußnote.

Ansonsten: der Performance-Vorteil von 2) gegenüber 1) ist verschwindend gering bis nicht vorhanden. So lange die Verkettung in einer Zeile passiert, sollte der Compiler das wegoptimieren (bei C# bin ich mir nicht sicher, Java tut es definitiv); mal abgesehen davon, dass ein Dutzend temporäre Strings das Kraut nicht fett machen. Und auch ansonsten ist die Verkettung mit dem +-Operator nur dann ein spürbares Problem, wenn sie in einer oft durchlaufenen Schleife stattfindet.
27.09.2011
Matthias Hlawatsch 13,2k 4 9
1
1 + 3 sind gleich performant (nach dem kompilieren sehen beide fast gleich aus), 3 dabei aber (imo) wesentlich einfacher zu schreiben/lesen ist. Demnach - wenn man sich auf die drei beschränken will - würde die Wahl eindeutig auf 3 Fallen.

Der einzige Unterschied zwischen 1 & 3 (das "fast") ist, dass bei 1

string stmt = "SELECT bierflasche FROM kühlschrank WHERE temperatur < 8";

und bei 3

string stmt = "SELECT\r\nbierflasche\r\nFROM kühlschrank\r\nWHERE temperatur < 8";

beim Kompilieren heraus kommt...

Option 2 ist im Übrigen nicht schneller, da erst mal ein StringBuilder bemüht werden muss (falls der nicht wegoptimiert werden kann), anstatt ein string direkt literal verwenden zu können.

StringBuilder schlägt
string.Concat()
(und damit "xx" + "yy"), wenn man mehrere Strings zusammenfügen will, die nicht zur Kompilierzeit bekannt sind.
27.09.2011
WolfgangKluge 1,0k 2 7
Doch, ich denke schon, dass der StringBuilder schneller ist, hier muss schließlich nur 1 Objekt erzeugt werden, bei der Konkatenation von 3 Strings wie in meinem Beispiel schon 3. Das ist jetzt aber Erbsenzählerei.
chriscolm 27.09.2011
So lange Du die strings als Konstanten zusammenfügst (und das machst Du), optimiert der compiler bereits. Du hast am Ende also nur einen einzigen string, der direkt verwendet werden kann (siehe oben).
StringBuilder lohnt sich nicht immer (bei 3 Zeilen schon gar nicht) und hat im Gegensatz zur direkten Verwendung den Nachteil, dass der string erst mal rein und danach auch wieder raus muss. Damit erstellst Du ein weiteres Objekt und kopierst Daten unnötig hin und her - ergo langsamer (wobei man das kaum merken wird).
Beim Zusammenfügen unbekannter strings (z.B. aus Dateiinhalt) geb ich Dir recht
WolfgangKluge 28.09.2011
1
Servus allerseits!
genau vor der Frage stand ich auch einmal. Ich habe dann den Instant SQL Formatter gefunden zu dem ich u.a. schon einen Vortrag in der DevGroup Frankfurt gehalten habe.

Hiermit kannst Du direkt kostenlos auf der Website SQL in verschiedenste Sprachen (C#, VB.Net, HTML, ...) formatieren lassen und wieder zurück. Das Ganze gibts dann auch kostenpflichtig als VS / SQL Studio Addin.

Ich nutze es auch um Code in einen String umwandeln zu lassen und dann im SQL Management Studio auszuführen. Da brauch man dann nicht extra den Breakpoint zu setzten und den String aus dem StringBuilder zu holen. Durch die Zeilenumbrüche die er ergänzt ist es auch in der Schnellansicht immer noch gut zu lesen.

Falls ich euch weiterhelfen konnte lasst es mich durch eine "Bewertung" wissen :-)

Viele Grüße,
Thomas Sczyrba

P.s. Umwandlungsbeispiele die ich über website generiert habe. Ausgangsbasis:

create function infFKfrom(@tbID int, @colID smallint) returns varchar(2000) as
begin declare @r varchar(2000), @a varchar(200)
select @r='', @a=''
DECLARE cs CURSOR FOR select FKfrom=convert(varchar(200),object_name(rkeyid)+'.'+r.[name])
from sysforeignkeys c join syscolumns f on c.fkeyid=f.[id] and c.fkey=f.colID
join syscolumns r on c.rkeyid=r.[id] and c.rkey=r.colID where fkeyID=@tbID and fkey=@colID
order by keyNo OPEN cs
FETCH NEXT FROM cs INTO @a
WHILE @@FETCH_STATUS = 0 BEGIN
select @r=@r+case when len(@r)>0 then ', ' else '' end+@a
FETCH NEXT FROM cs INTO @a
END CLOSE cs
DEALLOCATE cs
return(@r)
end
GO

Vollautomatisch wird das hier daraus:

StringBuilder  var1 = new StringBuilder();
var1.Append("CREATE FUNCTION Inffkfrom(@tbID INT, \n");
var1.Append(" @colID SMALLINT) \n");
var1.Append("RETURNS VARCHAR(2000) \n");
var1.Append("AS \n");
var1.Append(" BEGIN \n");
var1.Append(" DECLARE @r VARCHAR(2000), \n");
var1.Append(" @a VARCHAR(200) \n");
var1.Append(" \n");
var1.Append(" SELECT @r = '', \n");
var1.Append(" @a = '' \n");
var1.Append(" \n");
var1.Append(" DECLARE cs CURSOR FOR \n");
var1.Append(" SELECT fkfrom=CONVERT(VARCHAR(200), Object_name(rkeyid)+'.'+r.[name]) \n");
var1.Append(" FROM sysforeignkeys c \n");
var1.Append(" JOIN syscolumns f \n");
var1.Append(" ON c.fkeyid = f.[id] \n");
var1.Append(" AND c.fkey = f.colid \n");
var1.Append(" JOIN syscolumns r \n");
var1.Append(" ON c.rkeyid = r.[id] \n");
var1.Append(" AND c.rkey = r.colid \n");
var1.Append(" WHERE fkeyid = @tbID \n");
var1.Append(" AND fkey = @colID \n");
var1.Append(" ORDER BY keyno \n");
var1.Append(" \n");
var1.Append(" OPEN cs \n");
var1.Append(" \n");
var1.Append(" FETCH NEXT FROM cs INTO @a \n");
var1.Append(" \n");
var1.Append(" WHILE @@FETCH_STATUS = 0 \n");
var1.Append(" BEGIN \n");
var1.Append(" SELECT @r = @r + CASE \n");
var1.Append(" WHEN Len(@r) > 0 THEN ', ' \n");
var1.Append(" ELSE '' \n");
var1.Append(" END + @a \n");
var1.Append(" \n");
var1.Append(" FETCH NEXT FROM cs INTO @a \n");
var1.Append(" END \n");
var1.Append(" \n");
var1.Append(" CLOSE cs \n");
var1.Append(" \n");
var1.Append(" DEALLOCATE cs \n");
var1.Append(" \n");
var1.Append(" RETURN( @r ) \n");
var1.Append(" END");


StringBuilder var11 = new StringBuilder();
var11.Append(" \n");
var11.Append("GO ");


oder ohne StringBuilder das hier:

String var1 = "";
var1 = var1 +"CREATE FUNCTION Inffkfrom(@tbID INT, "+"\n";
var1 = var1 +" @colID SMALLINT) "+"\n";
var1 = var1 +"RETURNS VARCHAR(2000) "+"\n";
var1 = var1 +"AS "+"\n";
var1 = var1 +" BEGIN "+"\n";
var1 = var1 +" DECLARE @r VARCHAR(2000), "+"\n";
var1 = var1 +" @a VARCHAR(200) "+"\n";
var1 = var1 +" "+"\n";
var1 = var1 +" SELECT @r = '', "+"\n";
var1 = var1 +" @a = '' "+"\n";
var1 = var1 +" "+"\n";
var1 = var1 +" DECLARE cs CURSOR FOR "+"\n";
var1 = var1 +" SELECT fkfrom=CONVERT(VARCHAR(200), Object_name(rkeyid)+'.'+r.[name]) "+"\n";
var1 = var1 +" FROM sysforeignkeys c "+"\n";
var1 = var1 +" JOIN syscolumns f "+"\n";
var1 = var1 +" ON c.fkeyid = f.[id] "+"\n";
var1 = var1 +" AND c.fkey = f.colid "+"\n";
var1 = var1 +" JOIN syscolumns r "+"\n";
var1 = var1 +" ON c.rkeyid = r.[id] "+"\n";
var1 = var1 +" AND c.rkey = r.colid "+"\n";
var1 = var1 +" WHERE fkeyid = @tbID "+"\n";
var1 = var1 +" AND fkey = @colID "+"\n";
var1 = var1 +" ORDER BY keyno "+"\n";
var1 = var1 +" "+"\n";
var1 = var1 +" OPEN cs "+"\n";
var1 = var1 +" "+"\n";
var1 = var1 +" FETCH NEXT FROM cs INTO @a "+"\n";
var1 = var1 +" "+"\n";
var1 = var1 +" WHILE @@FETCH_STATUS = 0 "+"\n";
var1 = var1 +" BEGIN "+"\n";
var1 = var1 +" SELECT @r = @r + CASE "+"\n";
var1 = var1 +" WHEN Len(@r) > 0 THEN ', ' "+"\n";
var1 = var1 +" ELSE '' "+"\n";
var1 = var1 +" END + @a "+"\n";
var1 = var1 +" "+"\n";
var1 = var1 +" FETCH NEXT FROM cs INTO @a "+"\n";
var1 = var1 +" END "+"\n";
var1 = var1 +" "+"\n";
var1 = var1 +" CLOSE cs "+"\n";
var1 = var1 +" "+"\n";
var1 = var1 +" DEALLOCATE cs "+"\n";
var1 = var1 +" "+"\n";
var1 = var1 +" RETURN( @r ) "+"\n";
var1 = var1 +" END";


String var11 = "";
var11 = var11 +" "+"\n";
var11 = var11 +"GO ";


Oder einfach nur ein Formatiertes SQL Statement:

CREATE FUNCTION Inffkfrom(@tbID  INT,
@colID SMALLINT)
RETURNS VARCHAR(2000)
AS
BEGIN
DECLARE @r VARCHAR(2000),
@a VARCHAR(200)

SELECT @r = '',
@a = ''

DECLARE cs CURSOR FOR
SELECT fkfrom=CONVERT(VARCHAR(200), Object_name(rkeyid)+'.'+r.[name])
FROM sysforeignkeys c
JOIN syscolumns f
ON c.fkeyid = f.[id]
AND c.fkey = f.colid
JOIN syscolumns r
ON c.rkeyid = r.[id]
AND c.rkey = r.colid
WHERE fkeyid = @tbID
AND fkey = @colID
ORDER BY keyno

OPEN cs

FETCH NEXT FROM cs INTO @a

WHILE @@FETCH_STATUS = 0
BEGIN
SELECT @r = @r + CASE
WHEN Len(@r) > 0 THEN ', '
ELSE ''
END + @a

FETCH NEXT FROM cs INTO @a
END

CLOSE cs

DEALLOCATE cs

RETURN( @r )
END

GO
28.09.2011
Thomas Sczyrba 1,4k 1 2 9
Thomas Sczyrba 1,4k 1 2 9
+1 Gut zu wissen, dass es das Ding gibt - aber da er Variante 3 nicht kann, kommt er zumindest für C#-Formatierung für mich nicht in Frage.
Matthias Hlawatsch 28.09.2011
Hi Matthias. Variante 3 würde ja eigentlich nur heissen das du string stmt = @" vor mein drittes Beispiel packst und am ende den string wieder schliesst mit "; - Oder habe ich dich falsch verstanden?
Thomas Sczyrba 28.09.2011
Hm, vielleicht war ich da etwas vorschnell. Mit der dritten Variante könnte es gehen wie von Dir vorgeschlagen, plus Escapen von Sonderzeichen. In String-Literalen, die mit @ eingeleitet werden, muss man aber in C# nur die " escapen, oder hab ich was übersehen? Das wäre praktisch, denn die kommen in SQL-Befehlen selbst höchstens mal in String-Literalen vor, also fast nie. Und wenn, dann meckert der Compiler sehr schnell und deutlich.
Matthias Hlawatsch 28.09.2011
1
Ich würde Dir nicht empfehlen SQL-Statements direkt im Code zu plazieren.
Das ist imho nur für kleinere Projekte oder Webanwendungen empfehlenswert.

Bei größeren Projekten, kommt es leider öfters vor, dass sich die SQL-Abfragen relativ häufig ändern. Da will dann ein Geschäftsführer mal andere Spalten oder eine andere Sortierung.

Wenn dann für jede kleine SQL-Änderung ein neuer Client/DLL's ausgerollt werden muss - dann wirds teuer und die Anwender können während des Deployments nicht arbeiten :-(

Wenn Du einen SQL-Server verwendest, würde ich Dir empfehlen vorrangig Stored Procedures einzusetzen, die sind sehr performant und leicht anpassbar.

Eine beliebte Variante ist auch das Auslagern aller SQL-Statements in einer eigenen Tabelle, auf der Basis kannst Du dann auch ein Administrationstool erstellen.

LG, Michael
29.09.2011
mblaess 1,2k 1 9
mblaess 1,2k 1 9

Stelle deine Sql-Frage jetzt!