Laden...

[Erledigt] "Long Varchar" (Char[]) in SQL-Datenbank speichern – ADO.NET

Erstellt von pollito vor 8 Jahren Letzter Beitrag vor 8 Jahren 3.482 Views
pollito Themenstarter:in
314 Beiträge seit 2010
vor 8 Jahren
[Erledigt] "Long Varchar" (Char[]) in SQL-Datenbank speichern – ADO.NET

Verwendetes Datenbanksystem: Gupta SQLBase 11.7

Ich möchte mit ADO.NET einige Werte einer SQL-Datenbank aktualisieren, was auch problemlos geht, solange die Datenbankspalte kein Char-Array ist.

Ich verwende die Datenbank SQLBase 11.7 der Firma Gupta. Diese definiert für ihren Datentyp „Long Varchar“ (dient der Speicherung fast beliebig langer Zeichenarrays) die .NET-Entsprechung „Char[]“.

Bereits der erste Test mit einer Datenbankspalte dieses Typs schlägt fehl. Ich versuche, einen gerade eben eingelesenen Datensatz ohne Änderung der „Long Varchar“-Spalte zu aktualisieren – dazu ändere ich lediglich einen Feldwert der Spalte STATUS (Variable "f_STATUS"), deren Datentyp schlicht String ist.

public void dtsWrite_Test1()
{
	try
	{
		using (SQLBaseDataAdapter da = new SQLBaseDataAdapter($"select {f_ID}, {f_LFDNR}, {f_STATUS}, {f_ANLAGEN} from {db_DBKOMMPROTOKOLL} where ID = {CommID} and LFDNR = 1", con))
		{
			da.MissingSchemaAction = MissingSchemaAction.AddWithKey;

			using (DataSet ds = new DataSet())
			{
				con.Open();
				da.Fill(ds, db_DBKOMMPROTOKOLL);
				con.Close();

				ds.Tables[db_DBKOMMPROTOKOLL].Rows[0][f_STATUS] = "Änderung";

				using (DataSet ds1 = ds.GetChanges())
				{
					if (ds1 != null)
					{
						SQLBaseCommandBuilder cb = new SQLBaseCommandBuilder(da);

						con.Open();
						da.Update(ds1, db_DBKOMMPROTOKOLL);
						con.Close();
					}
				}
			}
		}
	}
	catch
	{
		throw;
	}
	finally
	{
		con?.Close();
	}
}


Die Variable "f_ANLAGEN" enthält den Namen der "Long Varchar"-Spalte "ANLAGEN". Diese wird im SELCT-Befehl des Datenadapters verwendet und somit beim Aufruf der Fill-Methode mit eingelesen. Nun ändere ich den eingelesenen Datensatz (ist nur einer), indem ich den Wert der Spalte "STATUS" (String) überschreibe. Weiter unten rufe ich die Update-Methode auf, die folgende Ausnahme wirft:

Fehlermeldung:
System.InvalidCastException
"Das Objekt des Typs "System.Char[]" kann nicht in Typ "System.String" umgewandelt werden."

Lasse ich im SELECT-Befehl die Spalte "ANLAGEN" weg, funktioniert die Update-Methode richtig. Also es hängt definitiv mit der "Long Varchar"-Spalte zusammen.

Versuche ich der "Long Varchar"-Spalte testhalber einen String zuzuweisen, schlägt das natürlich fehl:

ds.Tables[db_DBKOMMPROTOKOLL].Rows[0][f_ANLAGEN] = "Test";

Die Ausnahme lautet:

Fehlermeldung:
System.Data
Der Typ des Wertes stimmt nicht mit dem Spaltentyp überein. <Test> konnte nicht in der ANLAGEN-Spalte gespeichert werden. Erwarteter Typ: Char[].

Letzteres war auch zu erwarten...

Jetzt bin ich ratlos.

Was muss ich machen, um Char-Arrays mit ADO.NET speichern zu können?

Schönen Dank!

René

3.511 Beiträge seit 2005
vor 8 Jahren

Moin,

lass dieses String-Gefrickel weg und nutze Parameter: [Artikelserie] SQL: Parameter von Befehlen

Edit: Und setze direkt das Update ab und gehe nicht den überaus inperformanten Weg über DataSets/DataTables.

Gruß
Khalid

"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)

pollito Themenstarter:in
314 Beiträge seit 2010
vor 8 Jahren

Danke, das wäre der nächste Versuch, wenn das nicht geht. Das beantwortet jedoch nicht wirklich meine Frage, sondern zeigt mir nur einen Ausweg auf. Ich würde dennoch gerne wissen, warum das Schreiben eines gerade eingelesenen Datensatzes fehlschlägt, obwohl man kein "String-Gefricke" betrieben hat, sobald ein char[] beteiligt ist – das trägt nämlich zum Verständnis bei.

Ich selbst programmiere seit vielen Jahren Datenbankanwendungen unter dem Team Developer von Gupta, der keine Probleme mit "Long Varchar" hat. Nun muss ich mich in die Thematik ADO.NET einarbeiten und hier möchte ich schon wissen, warum ein Weg nicht geht – in diesem Fall warum ein DataSet bzw. DataTable mit char[] dieses Verhalten zeigt.

Bis auf den Datentyp (byte[] anstelle von char[] – Blob anstelle von Clob) beschreibt Microsoft u. a. hier, wie man das mit einem DataSet bzw. DataTable machen kann:

https://support.microsoft.com/en-us/kb/309158

D. h. prinzipiell geht das, nur in meinem Fall nicht – ich würde gerne wissen, warum es bei mir nicht funktioniert, unabhängig davon, welche Methode ich schließlich für den Datenbankzugriff verwende.

Nochmals vielen Dank!

René

3.825 Beiträge seit 2006
vor 8 Jahren

Hallo René,

Gupta gibt das Mapping von ihrem Datentyp Long Varchar mit char[] in .NET ab, probier das doch erstmal.
char[] wird in .NET nicht automatisch in string gecastet, deshalb die Fehlermeldung wenn Du das probierst.

char[] val = "Test".ToCharArray();
ds.Tables[db_DBKOMMPROTOKOLL].Rows[0][f_ANLAGEN] = val;

Meiner Meinung nach ist die Verwendung von DataSet und DataTable OK, ist ja so in .NET vorgesehen.

Es ist auch möglich dass der ADO.NET Treiber von Gupta Fehler enthält und ein Update von Long Varchar nicht funktioniert. Frag mal im Gupta Forum nach.

Probier mal den Datentyp Long Varchar nur zu lesen und beim Update wegzulassen.

Wenn das alles nicht geht dann musst Du das Update-Kommando selber bauen. Bitte mit Parametern.

Grüße Bernd

Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3

pollito Themenstarter:in
314 Beiträge seit 2010
vor 8 Jahren
char[] val = "Test".ToCharArray();  
ds.Tables[db_DBKOMMPROTOKOLL].Rows[0][f_ANLAGEN] = val;  

Vielen Dank! Das tue ich auch, indem ich z. B. den Wert, der gespeichert werden muss, explizit mit der Methode String.ToCharArray() konvertiere (siehe "dt.Rows[0][f_ANLAGEN] = ...;"):

		public void dtsWrite(int em_count)
		{
			try
			{
				using (SQLBaseDataAdapter da = new SQLBaseDataAdapter($"select {f_ID}, {f_LFDNR}, {f_STATUS}, {f_ANLAGEN} from {db_DBKOMMPROTOKOLL} where ID = {CommID} and LFDNR = 1", con))
				{
					da.MissingSchemaAction = MissingSchemaAction.AddWithKey;

					using (DataSet ds = new DataSet())
					{
						if (da.Fill(ds, db_DBKOMMPROTOKOLL) != 1)
						{
							throw new Exception($"Ersten Datensatz mit Kommunikations-ID = {CommID} nicht gefunden.");
						}

						DataTable dt = ds.Tables[db_DBKOMMPROTOKOLL];

						dt.Rows[0][f_STATUS] = dts[f_STATUS].Value;
						dt.Rows[0][f_ANLAGEN] = (em.emailpar[par.cmdEmail_attachment].Replace(Constants.StringSeparator, ";")).ToCharArray();

						using (DataSet ds1 = ds.GetChanges())
						{
							if (ds1 != null)
							{
								SQLBaseCommandBuilder cb = new SQLBaseCommandBuilder(da);

								con.Open();
								da.Update(ds1, db_DBKOMMPROTOKOLL);
								con.Close();
							}
						}
					}
				}
			}
			catch
			{
				throw;
			}
			finally
			{
				con?.Close();
			}
		}

Die Zuweisung wird ohne Probleme durchgeführt und danach steht erwartungsgemäß der geänderte Wert als char[] in diesem Feld. Wenn ich die Zuweisung testhalber nicht mache, steht der durch Fill() eingelesene char[]-Wert in diesem Feld. Will heißen, das Feld speichert richtig und die Fill-Methode füllt ebenfalls richtig. Will man jedoch speichern, dann tritt der Fehler in beiden Fällen auf.

Lasse ich in der SELECT-Anweisung "{f_ANLAGEN}" weg (also kein char[]-Feld mehr), so arbeitet die Update-Methode korrekt.

Langsan befürchte ich, dass Gupta hier ein Problem hat. Allerdings ist das Fragen in den Gupta- bzw. SQLBase-Gruppen nahezu zwecklos: Zunächst handelt es sich um paranoische, geschlossene Gruppen, so dass nur wenige Einsicht in diese haben und zusätzlich ist sehr wenig los und dementsprechend fallen die Antworten aus – meine letzten zwei Fragen wurden kaum gelesen und ich bekam auch nach Tagen keine Antwort.

Ich weiß, dass kann ich möglicherweise durch parametrisierte Befehle (sofern Gupta keinen Fehler hat) lösen. Beim Update ginge es auch, wenngleich viele Werte zu aktualisieren sind. Beim Insert sind es bereits 28 Felder, so dass hier ziemlich viel Arbeit m. E. um sonst zu bewältigen ist. Mit dem DataSet bzw, DataTable wäre das in der Tat eleganter (Performance spielt bei dieser Anwendung keine Rolle).

Nochmals vielen Dank!

René

16.842 Beiträge seit 2008
vor 8 Jahren

Parameter dienen u.a. der Sicherheit gegen SQL Injections, sodass es hier eigentlich gar keine Argumentationsgrundlage gibt, dass man diese "wegen Mehrarbeit" weglassen kann.
Mit Sicherheit spielt man nicht - und ich sehe hier ein riesiges Scheunentor, das SQL Injections prinzipiell durchlässt.
Würde bei jedem Audit durchfallen.

Dann fällt mir noch auf:

  • Typo bei der ExceptionMessage, $ fehlt
  • Man sollte keine Exception vom Typ "Exception" werfen, sondern immer konkrete Exception-Typen, zur Not auch eigene.
  • catch throw macht man nicht, verändert den Stacktrace. Einfach das catch weg lassen.
  • Und eben das wichtigste: keine String-Pfuscherei (nichts anderes ist es) bei SQL Befehlen.
pollito Themenstarter:in
314 Beiträge seit 2010
vor 8 Jahren

Danke, aber es hilft mir nicht weiter.

Sicherheit spielt hier (in meiner Anwendung) keine Rolle, da weder Anwender noch sonst wer irgendwas "injecten" kann. Die SQL-Befehle sind immer gleich und werden von der Anwendung erstellt, auch die veränderten Werte. Der Datenverkehr ist durch SSL gesichert.

Ich will keine Grundlagendiskussion führen, sondern in Erfahrung bringen, warum dieser Weg zu diesen Problemen führt. Wie ich das schlussendlich mache, steht auf einem anderen Blatt – ich möchte an dieser Stelle verstehen, wo das Problem liegt, denn nur so kann ich auch zukünftig Entscheidungen treffen, welche Technologie für die jeweilige Anwendung die richtige ist. Wenn ich jedoch von vorne herein etwas auslasse, weil es "unschön" ist und nur die eine Richtung einschlage, bin ich irgendwann blind auf einem Auge. Das will ich nicht.

Nochmals Dankeschön!

Edit:
Danke für das "$"

René

3.825 Beiträge seit 2006
vor 8 Jahren
using (DataSet ds1 = ds.GetChanges())
    {
    }

Ist überflüssig. Beim Update werden sowieso nur die geänderten Zeilen benutzt.

Ich weiß, dass kann ich möglicherweise durch parametrisierte Befehle (sofern Gupta keinen Fehler hat) lösen.

Nein, die Parameter werden den Fehler nicht lösen. Aber sie haben andere Auswirkungen.
Ich finde auch zu beachten dass Sonderzeichen in Stringwerten wie ' oder " beim Gefrickel immer zu Problemen führen, bei Datumswerten werden Monat und Tag schnell verwechselt.

Wenn es nicht um Performance geht kannst Du auch alles per DataSet machen und nur den einen Wert per SQL-Kommando updaten :

update {db_DBKOMMPROTOKOLL} set {f_ANLAGEN} = ... where ID = @ID ...

Grüße Bernd

Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3

pollito Themenstarter:in
314 Beiträge seit 2010
vor 8 Jahren

Ein kleiner Test zeigt, dass durch Verwendung von Parametern und das direkte Absetzen des Update-Befehls man in der Lage ist, "Long Varchar" in der Datenbank zu speichern.

SQLBaseCommand UpdateCmd = new SQLBaseCommand($"update {db_DBKOMMPROTOKOLL} set {f_ANLAGEN} = :b_ANLAGEN where {f_ID} = :b_ID and {f_LFDNR} = :b_LFDNR", con);

char[] anlagen = (em.emailpar[par.cmdEmail_attachment].Replace(Constants.StringSeparator, ";")).ToCharArray();

UpdateCmd.Parameters.AddWithValue("b_ANLAGEN", anlagen);
UpdateCmd.Parameters.AddWithValue("b_ID", Int32.Parse(CommID));
UpdateCmd.Parameters.AddWithValue("b_LFDNR", 1);

con.Open();
int i = UpdateCmd.ExecuteNonQuery();
con.Close();

Ich bin mir ziemlich sicher, dass die ADO.NET-Implementierung von SQLBase fehlerhaft ist, denn eine Suche in den wenigen Foren ergab, dass ab der Version 11 der Datenbank sich immer wieder Fehler in diesem Bereich eingeschlichen haben.

Leider helfen die Foren nicht wirklich weiter, denn man bekommt keine Antworten. Hier (SQLBase-11.7-Forum) z. B. sind folgende aktuelle Threads, die sich mit dieser Materie befassen, weder kommentiert, noch werden sie überhaupt besucht:

Der hier beschriebene Fehler (vom Kollegen eingestellt)

Ein anderer Fehler, der ebenfalls unbeantwortet bleibt

Man sollte etwas mehr von Bezahlware erwarten...

Nun mache ich das mit den selbst geschriebenen Update- und Insert-Befehlen mit Hilfe von Parametern und gut ist.

An dieser Stelle möchte ich mich bei euch herzlich bedanken und allen ein besinnliches Weihnachtsfest wünschen!

René

F
10.010 Beiträge seit 2004
vor 8 Jahren

Könntest du mir mal erklären warum du hier ständig das $ Zeichen missbrauchst?
Das hat in einem festen SQL Befehl nichts zu suchen und ist der Grund warum in diesem Thread ständig ( unnötiger weise ) auf Parameter verwiesen wird.

16.842 Beiträge seit 2008
vor 8 Jahren

Das hat er schon gesagt. Sicherheit sei nicht wichtig für ihn/die Anwendung 😃

pollito Themenstarter:in
314 Beiträge seit 2010
vor 8 Jahren

Könntest du mir mal erklären warum du hier ständig das $ Zeichen missbrauchst?
Das hat in einem festen SQL Befehl nichts zu suchen und ist der Grund warum in diesem Thread ständig ( unnötiger weise ) auf Parameter verwiesen wird.

Suche bitte nach dem $-Zeichen.

https://msdn.microsoft.com/de-de/magazine/dn879355.aspx

http://blogs.msdn.com/b/csharpfaq/archive/2014/11/20/new-features-in-c-6.aspx

Auch dir frohe Weihnachten!

René

16.842 Beiträge seit 2008
vor 8 Jahren

Er weis durchaus wofür es steht - aber String-Frickeleien gehören hat nicht in den DAL.
Mit dem Feedback musst Du Dich abfinden. 👍

PS: nur, weil Code dadurch einfacher zu lesen ist, wird er nicht besser.
So, und das muss man auch an Weihnachten sagen, ist es einfach schlechter Code. Sorry. 😉
Weder Sicherheit (in Form von Parametern) noch die Trennung von Verantwortlichkeiten (SoC) noch ein ordentlicher Codeaufbau (zB. Repository Pattern), keinerlei ordentliche Fehlerbehandlung.
Keinerlei Testbarkeit.

pollito Themenstarter:in
314 Beiträge seit 2010
vor 8 Jahren

Wo soll ich die Glaskugel hinschicken? Habt ihr was in den Augen?

Ein kleiner Test zeigt, dass...

Auch woanders im Thread erwähnt.

Leute, ich bedanke mich für die gute Hilfe, aber die immer wieder kehrenden Belehrungen à la Oberschulstudienrat müssen nicht sein, insbesondere wenn diese mit der Eingangsfrage nichts mehr zu tun haben. Hinweise immer gerne, aber am Thema vorbei nicht aufhören zu wollen, immer wieder an Lappalien herumzuackern, ist Experten unwürdig.

Das Thema wurde auch als erledigt gekennzeichnet. Für die Zukunft überlege ich, sobald ich eine für mich brauchbare Antwort bekomme, kein Feedback mehr zu geben, um somit solche nichtsbringende Beiträge zu vermeiden.

Nochmals herzlichen Dank für die Hilfe und ein schönes Weihnachtsfest!

René