Anmelden

Archiv verlassen und diese Seite im Standarddesign anzeigen : [C#] Parameter bei SQL-Server 2008


Gast
2011-08-03, 10:20:08
Hallo,
ich habe ein Problem mit der Parameter-Übergabe in SQL-Statements.
INSERT INTO Material (MaterialNr,Bezeichnung,Mandant) VALUES (@MaterialNr, @Bezeichnung, @Mandant)
Wenn ich dieses Statement ausführe, kommt die Fehlermeldung Must declare the scalar variable "@MaterialNr".

Wenn ich die Variable im Statement vorher deklariere...
DECLARE @MaterialNr varchar(4000), @Bezeichnung varchar(4000), @Mandant varchar(4000)
INSERT INTO Material (MaterialNr,Bezeichnung,Mandant) VALUES (@MaterialNr, @Bezeichnung, @Mandant)
...bekomme ich die Fehlermeldung Cannot insert the value NULL into column 'MaterialNr', obwohl OdbcCommand.Parameters gefüllt ist.

Wenn ich ? als Platzhalter verwende, funktioniert alles. Jedoch will ich das vermeiden, weil das finale Statement so aussehen soll:
UPDATE Material SET Bezeichnung=@Bezeichnung WHERE MaterialNr=@MaterialNr AND Mandant=@Mandant
IF (@@ROWCOUNT = 0) BEGIN INSERT INTO Material (MaterialNr,Bezeichnung,Mandant) VALUES (@MaterialNr, @Bezeichnung, @Mandant) END
Mit ? müßte ich die gleichen Parameter mehrfach übergeben.

PS: Ich verwende die Odbc-Klassen, weil das Programm nicht zwingend MS-SQL-Server voraussetzen soll.

Gast
2011-08-03, 11:14:52
Kannst du mal den passenden Abschnitt aus deinem Quellcode posten?

Ich habe mir ein paar Operation in einer Klasse gekapselt. Sieht ungefähr so aus:

public void Insert(string table, SqlParameter[] parameters)
{
StringBuilder sql = new StringBuilder();
StringBuilder values = new StringBuilder();

sql.AppendFormat ("INSERT INTO {0} (", table);

foreach (SqlParameter parameter in parameters)
{
values.AppendFormat("{0},", parameter.ParameterName);
sql.AppendFormat("[{0}],", parameter.ParameterName.Substring(1));
}

values.Remove(values.Length - 1, 1);
sql.Remove(sql.Length - 1, 1);

sql.AppendFormat(") VALUES ({0})", values.ToString());

SqlConnection connection = new SqlConnection(_SqlString.ConnectionString);
connection.Open();

SqlCommand command = connection.CreateCommand();
command.CommandText = sql.ToString();
command.Parameters.AddRange(parameters);

command.ExecuteNonQuery();
connection.Close();
}


Aufruf dann so:


List<SqlParameter> parameters = new List<SqlParameter>();

parameters.Add(new SqlParameter("@SettingId", Guid.NewGuid());
parameters.Add(new SqlParameter("@Setting", "Bla"));
parameters.Add(new SqlParameter("@Data", "Blub"));

Insert("Settings", parameters.ToArray());


Müsstest bei dir eigentlich nur die Sqlxxx durch Odbcxxx ersetzen. Syntax sollte gleich sein.

samm
2011-08-03, 14:48:20
Gast, der TS möchte nicht MSSQL-spezifisch programmieren und hat Probleme mit den Parametern einer bestimmten Query, da sind Tipps mit Query-Konstruktion und Feldnamen in eckigen Klammern erstmal ablenkend ;) Ansonsten, wie Gast sagt: Quellcode bitte ;) Allgemein zu Bindung von benannten Parametern: http://msdn.microsoft.com/en-us/library/ms715435%28v=vs.85%29.aspx
Sie können per ODBC nur in SPs verwendet werden - vielleicht liegt schon hier das Problem, und der Grund, weshalb es mit "?" funktioniert.

Gast
2011-08-03, 15:09:41
Hab eine Lösung gefunden:

DECLARE @MaterialNr varchar(4000) = ?, @Bezeichnung varchar(4000) = ?, @Mandant varchar(4000) = ?
UPDATE Material SET Bezeichnung=@Bezeichnung WHERE MaterialNr=@MaterialNr AND Mandant=@Mandant
IF (@@ROWCOUNT = 0) BEGIN INSERT INTO Material (MaterialNr,Bezeichnung,Mandant) VALUES (@MaterialNr, @Bezeichnung, @Mandant) END

Matrix316
2011-08-03, 20:08:02
Ähm, da fehlt doch irgendwie noch was. ;) Sollte das in einer Stored procedure sein oder wie werden denn die Parameter übergeben? Normalerweise musst du eigentlich nix extra declaren, wenn du eine Stored Procedure nimmst, wiel die Parameter wie bei einer normalen Funktion mit übergeben werden (klar werden die dann auch declared, aber nicht nochmal extra in der Funktion). Wie nutzt du überhaupt die SQL Abfrage? In C#? VB? C++?

Die ersten beiden Fehler aus dem Ausgangsposting sind eigentlich logisch, wenn du die Abfragen einfach so ausführst.

xxxgamerxxx
2011-08-03, 22:40:19
Gast, der TS möchte nicht MSSQL-spezifisch programmieren und hat Probleme mit den Parametern einer bestimmten Query, da sind Tipps mit Query-Konstruktion und Feldnamen in eckigen Klammern erstmal ablenkend ;)

Der zweite Gast hat vollkommen Recht. Da ist auch nix MSSQL spezifisches. Wenn man nicht den leichtsinnigen Fehler macht und die Parameter im Query String mit var.ToString() zusammenfügt, dann verwendet man bei ADO.NET korrekterweise Parameter Klassen (SqlParameter usw.).

Gast_samm
2011-08-04, 12:26:54
Nichts MSSQL-spezifisches? Doch, Feldnamen in eckigen Klammern sind MSSQL-spezifisch, und SqlParameter ebenfalls. Ja, klar ist es besser, die Parameter-Klassen zu verwenden statt einen String zusammenzuschustern, dem will ich gar nicht widersprechen :) Insofern noch etwas, was gegen den Vorschlag des Gastes spricht, der nämlich genau das tut (Variablen sql und values enthalten den extrahierten Inhalt der SqlParameter-Objekte ;))

Gnafoo
2011-08-04, 14:06:07
Nachdem ich in meinen Projekten nichts mehr gefunden habe, hat mich Google Code freundlicherweise mit einem Beispiel versorgt:

http://code.google.com/p/blogenginemod/source/browse/trunk/BlogEngine.Core/Providers/DbBlogProvider.cs?r=2#245 (InsertPost-Methode)

Das ist afair die beste Art und Weise das zu machen. Man holt sich eine DbProviderFactory und konstruiert damit die Parameter-Objekte etc. Dann funktioniert der Code auch unabhängig von der Datenbank.

Dann sollte man DbConnection und DbCommand mit „using“ kapseln, so dass alles schön aufgeräumt wird. Im Query selber verwendet man diese Platzhalter mit „@“, die den Parameternamen entsprechen. Dadurch werden die Parameter automatisch an den entsprechenden Stellen eingesetzt, ohne dass man das Risiko einer SQL-Injection eingehen muss.

Und ja das ist nicht MSSQL-Spezifisch. DbParameter, DbCommand und co. sind Basisklassen und die spezifischen Klassen wie SqlCommand erben davon. Wenn man bei GetFactory einen anderen Provider-Namen angibt, dann funktioniert dasselbe auch mit MySQL oder anderen Datenbanken (vorausgesetzt ein entsprechender Provider ist installiert).

Soweit ich weiß kann sich das Präfix allerdings je nach Datenbank ändern (um Konflikte mit dem SQL-Dialekt auszuschließen evtl.). MySql verwendet afair „?“, daher wohl auch der zusätzliche Code mit „parmPrefix“.

xxxgamerxxx
2011-08-04, 14:32:57
Nichts MSSQL-spezifisches? Doch, Feldnamen in eckigen Klammern sind MSSQL-spezifisch, und SqlParameter ebenfalls. Ja, klar ist es besser, die Parameter-Klassen zu verwenden statt einen String zusammenzuschustern, dem will ich gar nicht widersprechen :) Insofern noch etwas, was gegen den Vorschlag des Gastes spricht, der nämlich genau das tut (Variablen sql und values enthalten den extrahierten Inhalt der SqlParameter-Objekte ;))

Welche eckigen Klammern? Das eine ist .NET Ebene und das andere ist SQL Ebene. Und in ADO.NET verwendet man für die Übergabe der Parameter am besten eine Parameter Klassen. Sei es nun Sqlxxx, Odbcxxx oder die generische Dbxxx Klassen.

samm
2011-08-05, 02:14:19
Welche eckigen Klammern?Der Gast bastelt hier relativ umständlich einen Query-String:sql.AppendFormat ("INSERT INTO {0} (", table);

foreach (SqlParameter parameter in parameters)
{
values.AppendFormat("{0},", parameter.ParameterName);
sql.AppendFormat("[{0}],", parameter.ParameterName.Substring(1));
}

values.Remove(values.Length - 1, 1);
sql.Remove(sql.Length - 1, 1);

sql.AppendFormat(") VALUES ({0})", values.ToString());
Der z.B. lautet "INSERT INTO mytable([field1],[field2]) VALUES ('field1','field2')"

Das eine ist .NET Ebene und das andere ist SQL Ebene.Brauchst du mir nicht erklären ;)

Und in ADO.NET verwendet man für die Übergabe der Parameter am besten eine Parameter Klassen. Sei es nun Sqlxxx, Odbcxxx oder die generische Dbxxx Klassen.Ack. Wie Gnafoo sagt, mit der Factory arbeiten, Query mit Parametern bauen etc.
Der Gast hingegen benutzt die Parameter nur als Speicher für Feldnamen und Inhalt. Das addrange könnte er sich auch gleich sparen, da er die ganze Query schon gebastelt hat - sie verwendet keine Parameter, so wie er sie gebaut hat. Beispielstring siehe oben. Übrigens macht auch die Benutzung der Substring-Methode wenig Sinn - alles in allem ziemlicher Quarkcode, den der Gast fabriziert hat. Die Query-Syntax mit Feldnamen in eckigen Klammern sowie die Verwendung von SqlParameter etc. ist MSSQL-spezifisch (wobei so wie er diese SqlParameter-Objekte benutzt eigentlich nicht, da ists ja eben nur ein String-Speicher) - ist jetzt klarer, was ich meinte?

Oder es ist schon spät und ich hab deswegen einen Konten im Hirn^^


Das ganz allgemeine Vorgehen, xyzParameter-Objekte zu verwenden, ist *nicht* MSSQL-spezfisch. Schön abstrahieren und injection-sicher machen, wie Gnafoo sagt ftw.

Soweit ich weiß kann sich das Präfix allerdings je nach Datenbank ändern (um Konflikte mit dem SQL-Dialekt auszuschließen evtl.).Ist leider so. Oracle würde z.B. einen Doppelpunkt davorstellen.

xxxgamerxxx
2011-08-05, 10:00:13
Der Gast bastelt hier relativ umständlich einen Query-String:
Der z.B. lautet "INSERT INTO mytable([field1],[field2]) VALUES ('field1','field2')"


Der String lautet: "INSERT INTO mytable([field1],[field2]) VALUES (@field1,@field2)"

Schau dir den Code mal genau an. Im Sql StringBuilder wird das Insert mit den Spaltennamen zusammengebaut. Deswegen auch Substring(1), weil der Klammeraffe vom Parametername abgeschnitten wird.

Und die Values beinhalten dann nur den Parameternamen. Also werden natürlich Parameter verwendet. In seinem Fall sind dann eben Spaltennamen und Parameternamen gleich.

Und wenn man seinen Sql Code in die Anwendungslogik kodiert, dann ist man natürlich auch in gewisser Weise an das DBMS gebunden.

Ansonsten muss der Threadstarter eben Sprocs nehmen und das Parameterprefix und andere Eigenheiten in irgend eine Textdatei speichern und dann dynamisch beim Erzeugen der Parameter davor hängen.

Der Threadstarter kann sich den Code ja vom anderen Gast ja nach belieben abändern. Deswegen musst du seinen Post nicht als wertlos hinstellen.

samm
2011-08-05, 17:51:48
Schau dir den Code mal genau an. Im Sql StringBuilder wird das Insert mit den Spaltennamen zusammengebaut. Deswegen auch Substring(1), weil der Klammeraffe vom Parametername abgeschnitten wird.Danke, da hast du recht, war wohl wirklich zu spät gestern *gääähn*^^
Deswegen musst du seinen Post nicht als wertlos hinstellen.:)