(Vorbemerkung: Nicht, dass jmd denkt, ich sei der einzige Spinner, der DbParameter als "Must-Use - No-Excuces" propagiert - dasselbe kann man zB auch hier nachlesen - (halt mit imo umständlicheren Code-Lösungen, und für c#).)
Immer wieder sieht man DB-Zugriffe, bei denen Werte an die Datenbank gesendet werden, die direkt in den CommandText eingefrickelt sind.
Also mal paar willkürliche Beispiele der letzten Tage:
Diese Vorgehensweise ist unleserlich, fehleranfällig und v.a. kriminell fahrlässig im Umgang mit der Datenbank des Datenbankbesitzers und den Daten der Programm-Nutzer.
NoGo ists, weil vollkommen unnötig, denn man kann mit demselben oder sogar weniger Aufwand auch eine sicherere Kommunikation proggen, indem man DbParameter verwendet.
Und ist ja auch klar, wo man solchen lernt: Galileio Openbook, wo sonst? (oder - ganz hardcore: YouTube ).
Also falls jmd. VB erlernen möchte, ohne sich von Galileio verderben zu lassen, empfehle ich dieses Buch Lesen
Ich persönlich halte von selbstgebastelten DbCommands ja insgesamt garnichts.
Bei mir gibts nur DataAdapter, mit denen man gleich ganze DataTables befüllt, und über die man auch die Insert-, Delete- und Update-Commands laufen lässt - ohne eine Extra-Zeile dafür coden zu müssen.
Aber da typisierte Datasets und datengebundene Datagridviews sich einiger Unbeliebtheit erfreuen, stelle ich hier wenigstens mal eine Extension-Methode vor, mit der man sich ein parametrisiertes Command erzeugen kann, mit weniger Aufwand, und lesbarer als das String-Gefrickel.
Die Methode:
Das ist jetzt die Lösung für SqlCe. Für andere DBProvider muß natürlich jeweils eine annere Extension geschrieben werden, etwa für Access müsste SqlCeConnection, SqlCeCommand ausgetauscht werden durch OleDbConnection, OleDbCommand.
Also eine string-frickelige Abfrage mag so aussehen:
Demgegenüber sähe die Verwendung meiner Extension so aus:
Wie gesagt, ich halte insgesamt nicht viel davon, aber ich habs mirmal gegeben, ein paar ListViews damit zu befüllen:
viel Code für wenig Funktionalität
Hier dieselbe Funktionalität, zuzüglich Ändern und Abspeichern, unter Verwendung der DBExtensions:
wenig Code für viel Funktionalität
Das ist jetzt nur ein Viertel des anderen Codes, kann dafür aber auch Sortieren, Editieren und Abspeichern.
Auch die Darstellung ist inhaltsreicher, es werden nämlich die Namen der Bearbeiter und Kunden angezeigt, wo eine ListView nur mit ID-Nummern aufwarten kann (oder extra-Aufwand erforderte).
Vergleiche: mit:
Die Combo-DropDowns im DGV deuten übrigens an, dass man auch die Bearbeiter und Kunden editieren kann, nämlich indem man das DropDown öffnet, und einen anneren Bearbeiter/Kunden anwählt.
Zur Solution:
Habe ich in VB2008 gemacht, und als DB eine SqlCe-Datenbank genommen. Der SqlCe-Provider scheint allmählich Access an allgemeiner Verfügbarkeit zu überholen.
Im Sample ist auch die neuesete Version der DBExtensions drin - ich bin einfach zu faul, die ganze Funktionalität immer wieder neu "from the Scratch" zu erfinden.
Anhang:
Da OleDB als einziges Datenbank-System benannte Parameter im CommandText nicht akzeptiert, muß die CreateConnectionX-Funktion für OleDb etwas abweichend formuliert werden:
Zu Antwort-Posts:
Ich täte euch bitten, Fragen hierzu im HauptForum zu klären. Diese VBP-Abteilung heißt "Tipps & Tricks und Tutorials", und ist nicht dazu gedacht, spezielle Einzelfall-Probleme zu lösen.
Immer wieder sieht man DB-Zugriffe, bei denen Werte an die Datenbank gesendet werden, die direkt in den CommandText eingefrickelt sind.
Also mal paar willkürliche Beispiele der letzten Tage:
VB.NET-Quellcode
- dt = fillControl("Select distinct(Zahl) from Training where system = '" & getSystem() & "' and versuchsleiter='" & Form1.comboTechnican.Text & "' and session_date > sysdate - 28 and tier_nr < 9999 order by Zahl asc")
- 'oder
- Dim sql As String = "SELECT COUNT (*) FROM Tabelle WHERE NichtsGefangen = FALSE " & If(UserId <> 0, "User = " & UserId & """, "")
- 'oder
- Dim SQLAbfrage As String = "UPDATE `benutzer` SET akku = '" + tb_akku.Text - 1 + "' WHERE `Benutzername` = '" + Eingang.lbl_name.Text + "'"
Diese Vorgehensweise ist unleserlich, fehleranfällig und v.a. kriminell fahrlässig im Umgang mit der Datenbank des Datenbankbesitzers und den Daten der Programm-Nutzer.
NoGo ists, weil vollkommen unnötig, denn man kann mit demselben oder sogar weniger Aufwand auch eine sicherere Kommunikation proggen, indem man DbParameter verwendet.
Und ist ja auch klar, wo man solchen lernt: Galileio Openbook, wo sonst? (oder - ganz hardcore: YouTube ).
Also falls jmd. VB erlernen möchte, ohne sich von Galileio verderben zu lassen, empfehle ich dieses Buch Lesen
Ich persönlich halte von selbstgebastelten DbCommands ja insgesamt garnichts.
Bei mir gibts nur DataAdapter, mit denen man gleich ganze DataTables befüllt, und über die man auch die Insert-, Delete- und Update-Commands laufen lässt - ohne eine Extra-Zeile dafür coden zu müssen.
Aber da typisierte Datasets und datengebundene Datagridviews sich einiger Unbeliebtheit erfreuen, stelle ich hier wenigstens mal eine Extension-Methode vor, mit der man sich ein parametrisiertes Command erzeugen kann, mit weniger Aufwand, und lesbarer als das String-Gefrickel.
Die Methode:
VB.NET-Quellcode
- Public Module SqlCeExtensions
- ''' <summary> allgemeine Methode, aus einer SqlCeConnection, einem CommandText und optionalen Werten
- ''' ein parametrisiertes SqlCeCommand zu erstellen </summary>
- ''' <remarks>Vorrausgeetzt ist, dass die übergebenen values Datentypen haben, die zu den in der Datenbank
- ''' festgelegten Spalten passen. Eine Überprüfung findet nicht statt.</remarks>
- <Runtime.CompilerServices.Extension()> _
- Public Function CreateCommandX(ByVal con As SqlCeConnection, ByVal commandText As String, ByVal ParamArray values As Object()) As SqlCeCommand
- Dim splits = commandText.Split("?"c) ' Parameter-Platzhalter '?' identifizieren
- If splits.Count <> values.Length + 1 Then Throw New ArgumentException( _
- "Anzahl der Argumente passt nicht zur Anzahl der Parameter-Platzhalter im CommandText")
- Dim cmd = New SqlCeCommand()
- ' für jeden Platzhalter einen DbParameter adden, und den Param-Namen an den Platzhalter schreiben
- For i = 0 To values.Length - 1
- Dim param = cmd.Parameters.AddWithValue("@a" & i, values(i))
- splits(i) = splits(i) & param.ParameterName
- Next
- cmd.CommandText = String.Concat(splits)
- cmd.Connection = con
- Return cmd
- End Function
- End Module
Also eine string-frickelige Abfrage mag so aussehen:
VB.NET-Quellcode
- 'Der normale NoGo-Progger täte gar Textboxen verwenden (statt hier: DateTimePicker), sodass Angreifer dort ihre Sql-Injection-Angriffe reinschreiben können.
- Dim sql = "Select * From [Bestellung] where [Datum] between #" & dtpVon.Value & "# and #" & dtBis.Value & "#"
- Dim cmd = New SqlCeCommand(sql, _Con)
Demgegenüber sähe die Verwendung meiner Extension so aus:
Wie gesagt, ich halte insgesamt nicht viel davon, aber ich habs mirmal gegeben, ein paar ListViews damit zu befüllen:
VB.NET-Quellcode
- Imports System.Data.SqlServerCe
- Public Class frmOhneDataset
- Private _Con As New SqlCeConnection(My.Settings.BestellungenConnectionString)
- Private Sub frmOhneDataset_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
- Me.AlignOnTop()
- End Sub
- Private Sub LadeKundenToolStripMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles LadeKundenToolStripMenuItem.Click, LadeBearbeiterToolStripMenuItem.Click, LadeBestellungenToolStripMenuItem.Click, btLadeGefiltert.Click, ClearAllToolStripMenuItem.Click
- Select Case True
- Case sender Is LadeKundenToolStripMenuItem
- LadeKunden()
- Case sender Is LadeBearbeiterToolStripMenuItem
- LadeBearbeiter()
- Case sender Is LadeBestellungenToolStripMenuItem
- LadeBestellungen()
- Case sender Is ClearAllToolStripMenuItem
- For Each lv In New ListView() {lvKunde, lvBearb, lvBest}
- lv.Items.Clear()
- Next
- Case sender Is btLadeGefiltert
- LadeBestellungenGefiltert()
- End Select
- End Sub
- Private Sub LadeKunden()
- _Con.Open()
- Using cmd = _Con.CreateCommandX("Select * From [Kunde]"), rd = cmd.ExecuteReader
- While rd.Read
- Dim lvi = lvKunde.Items.Add(rd(0).ToString)
- For i = 1 To rd.VisibleFieldCount - 1
- lvi.SubItems.Add(rd(i).ToString)
- Next
- End While
- End Using
- _Con.Close()
- End Sub
- Private Sub LadeBearbeiter()
- _Con.Open()
- Using cmd = _Con.CreateCommandX("Select * From [Bearbeiter]"), rd = cmd.ExecuteReader
- While rd.Read
- Dim lvi = lvBearb.Items.Add(rd(0).ToString)
- For i = 1 To rd.VisibleFieldCount - 1
- lvi.SubItems.Add(rd(i).ToString)
- Next
- End While
- End Using
- _Con.Close()
- End Sub
- Private Sub LadeBestellungen()
- _Con.Open()
- Using cmd = _Con.CreateCommandX("Select * From [Bestellung]"), rd = cmd.ExecuteReader
- While rd.Read
- Dim lvi = lvBest.Items.Add(rd(0).ToString)
- For i = 1 To rd.VisibleFieldCount - 1
- lvi.SubItems.Add(rd(i).ToString)
- Next
- End While
- End Using
- _Con.Close()
- End Sub
- Private Sub LadeBestellungenGefiltert()
- _Con.Open()
- Using cmd = _Con.CreateCommandX( _
- "Select * From [Bestellung] where [Datum] between ? and ?", dtpVon.Value, dtBis.Value), _
- rd = cmd.ExecuteReader
- While rd.Read
- Dim lvi = lvBest.Items.Add(rd(0).ToString)
- For i = 1 To rd.VisibleFieldCount - 1
- lvi.SubItems.Add(rd(i).ToString)
- Next
- End While
- End Using
- _Con.Close()
- End Sub
- Private Sub NoGo()
- 'würde vmtl. crashen, weil die Datumse automatisch konvertiert würden, und zwar falsch.
- 'Aber normale NoGo-Progger verwenden eh Textboxen, sodass Angreifer dort ihre Sql-Injection-Angriffe reinschreiben können.
- Dim sql = "Select * From [Bestellung] where [Datum] between " & dtpVon.Value & " and " & dtBis.Value
- Dim cmd = New SqlCeCommand(sql, _Con)
- '...
- End Sub
- Private Sub LadeGefiltert_OhneExtensionMethode()
- 'klassische Verwendung von DbParametern: etwas umständlich, weil zw. Command erstellen und Command abfahren noch die Parameter geaddet wern müssen
- 'etwas anfällig, weil die Parameternamen zweimal identisch festgelegt werden müssen.
- Dim sql = "Select * From [Bestellung] where [Datum] between @von and @bis"
- _Con.Open()
- Using cmd = New SqlCeCommand(sql, _Con)
- 'DBParameter vereiteln Sql-Injection und konvertieren i.a. typ-richtig
- cmd.Parameters.AddWithValue("@von", dtpVon.Value)
- cmd.Parameters.AddWithValue("@bis", dtBis.Value)
- Using rd = cmd.ExecuteReader
- While rd.Read
- Dim lvi = lvBest.Items.Add(rd(0).ToString)
- For i = 1 To rd.VisibleFieldCount - 1
- lvi.SubItems.Add(rd(i).ToString)
- Next
- End While
- End Using
- End Using
- _Con.Close()
- End Sub
- End Class
- Public Module SqlCeExtensions
- ''' <summary>
- ''' allgemeine Methode, aus einer SqlCeConnection, einem CommandText und optionalen Werten ein parametrisiertes SqlCeCommand zu erstellen
- ''' </summary>
- <Runtime.CompilerServices.Extension()> _
- Public Function CreateCommandX(ByVal con As SqlCeConnection, ByVal commandText As String, ByVal ParamArray values As Object()) As SqlCeCommand
- Dim splits = commandText.Split("?"c)
- If splits.Count <> values.Length + 1 Then Throw New ArgumentException( _
- "Anzahl der Argumente passt nicht zur Anzahl der Parameter-Platzhalter im CommandText")
- Dim cmd = New SqlCeCommand()
- For i = 0 To values.Length - 1
- Dim param = cmd.Parameters.AddWithValue("@a" & i, values(i))
- splits(i) = splits(i) & param.ParameterName
- Next
- cmd.CommandText = String.Concat(splits)
- cmd.Connection = con
- Return cmd
- End Function
- End Module
Hier dieselbe Funktionalität, zuzüglich Ändern und Abspeichern, unter Verwendung der DBExtensions:
VB.NET-Quellcode
- Imports System.Data.Common
- Imports System.Data.SqlServerCe
- Public Class frmMainM_N
- Private Sub Form_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
- Dim adp = New DatasetAdapter( _
- SqlCeProviderFactory.Instance, My.Settings.BestellungenConnectionString, _
- ConflictOption.OverwriteChanges)
- Me.AlignOnTop.BestellungenDts.Register(Me).Adapter(adp).Fill()
- AddHandler Me.FormClosing, BestellungenDts.HandleFormClosing
- End Sub
- Private Sub MenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) _
- Handles ReloadToolStripMenuItem.Click, SaveToolStripMenuItem.Click, btLadeGefiltert.Click
- Select Case True
- Case sender Is ReloadToolStripMenuItem
- BestellungenDts.Fill()
- Case sender Is SaveToolStripMenuItem
- BestellungenDts.Save(Me)
- Case sender Is btLadeGefiltert
- BestellungenDts.Bestellung.Fill("where [Datum] between ? and ?", dtpVon.Value, dtBis.Value)
- End Select
- End Sub
- End Class
Auch die Darstellung ist inhaltsreicher, es werden nämlich die Namen der Bearbeiter und Kunden angezeigt, wo eine ListView nur mit ID-Nummern aufwarten kann (oder extra-Aufwand erforderte).
Vergleiche: mit:
Die Combo-DropDowns im DGV deuten übrigens an, dass man auch die Bearbeiter und Kunden editieren kann, nämlich indem man das DropDown öffnet, und einen anneren Bearbeiter/Kunden anwählt.
Zur Solution:
Habe ich in VB2008 gemacht, und als DB eine SqlCe-Datenbank genommen. Der SqlCe-Provider scheint allmählich Access an allgemeiner Verfügbarkeit zu überholen.
Im Sample ist auch die neuesete Version der DBExtensions drin - ich bin einfach zu faul, die ganze Funktionalität immer wieder neu "from the Scratch" zu erfinden.
Anhang:
Da OleDB als einziges Datenbank-System benannte Parameter im CommandText nicht akzeptiert, muß die CreateConnectionX-Funktion für OleDb etwas abweichend formuliert werden:
VB.NET-Quellcode
- ''' <summary>
- ''' allgemeine Methode, aus einer OleDbConnection, einem CommandText und optionalen Werten ein parametrisiertes OleDbCommand zu erstellen
- ''' </summary>
- ''' <remarks>Vorrausgeetzt ist, dass die übergebenen values Datentypen haben, die zu den in der Datenbank
- ''' festgelegten Spalten passen. Eine Überprüfung findet nicht statt.</remarks>
- <Runtime.CompilerServices.Extension()> _
- Public Function CreateCommandX(ByVal con As OleDb.OleDbConnection, ByVal commandText As String, ByVal ParamArray values As Object()) _
- As OleDb.OleDbCommand
- If commandText.Split("?"c).Count <> values.Length + 1 Then Throw New ArgumentException( _
- "Anzahl der Argumente passt nicht zur Anzahl der Parameter-Platzhalter im CommandText")
- CreateCommandX = New OleDb.OleDbCommand() With {.CommandText = commandText, .Connection = con}
- For i = 0 To values.Length - 1
- CreateCommandX.Parameters.AddWithValue("@a" & i, values(i))
- Next
- End Function
Zu Antwort-Posts:
Ich täte euch bitten, Fragen hierzu im HauptForum zu klären. Diese VBP-Abteilung heißt "Tipps & Tricks und Tutorials", und ist nicht dazu gedacht, spezielle Einzelfall-Probleme zu lösen.
Dieser Beitrag wurde bereits 13 mal editiert, zuletzt von „ErfinderDesRades“ ()