SqlCommandBuilder Update Command is Generating Command but Not Updating Database From DataGridView - sql

I am inheriting a form class (Form1) for Form 2. Form 1 code works without error. In Form 1, I use SqlCommandBuilder(SQL.DBDA).GetUpdateCommand to generate the update command for my Datagrid to pass to SQL data table which again works perfectly and the table is updated successfully. The SQL command text for Form 1 Update is shown here:
In Form 2, I write the following for the update command, where the only difference is Selecting the Table shown here:
SQL.ExecQuery("SELECT * FROM dtbRateVerse;")
SQL.DBDA.UpdateCommand = New SqlClient.SqlCommandBuilder(SQL.DBDA).GetUpdateCommand
MsgBox(SQL.DBDA.UpdateCommand.CommandText)
SQL.DBDA.Update(SQL.DBDT)
The command text for this update command is shown here:
It is not dissimilar to the successful update command shown in Form1 (image 1). Still, no data is passed to the SQL from the Gridview.
I also tried writing a dynamic Update statement without using the command builder shown below. The text of this statement generates an accurate SQL command but again, nothing passed to the database. This code is shown here:
For i = 1 To colEnd
colName.Add("[" & DataGridView1.Columns(i).HeaderText.ToString & "]")
Next
For i = 1 To colEnd
For y = 0 To Me.DataGridView1.RowCount - 1
For n = 1 To colEnd
gridVals.Add(DataGridView1.Rows(y).Cells(n).Value.ToString)
Next
With Me.DataGridView1
SQL.AddParam("#PrimKey", .Rows(y).Cells(0))
cmdUpdate = "UPDATE " & tbl_Name & " SET " & colName.Item(i - 1) & "=" & gridVals.Item(i - 1) & " WHERE ID=#PrimKey;"
SQL.ExecQuery(cmdUpdate)
End With
Next
Next
If anyone has any ideas/ solutions on what I need to do to get the update command working properly, I'd really appreciate it. Thanks!
Added ExecQuery methdod per request below:
Public Class SQLControl
Private DBConnect As New SqlConnection("SERVER STRING HERE")
Private DBCmd As SqlCommand
'DB DATA
Public DBDA As SqlDataAdapter
Public DBDT As DataTable
'QUERY PARAMETERS
Public Params As New List(Of SqlParameter)
'QUERY STATISTICS
Public RecordCount As Integer
Public Exception As String
Public Sub New()
End Sub
'ALLOW CONNECTION STRING OVERRIDE
Public Sub New(ConnectionString As String)
DBConnect = New SqlConnection(ConnectionString)
End Sub
'EXECUTE QUERY SUB
Public Sub ExecQuery(Query As String)
'RESET QUERY STATS
RecordCount = 0
Exception = ""
Try
DBConnect.Open()
'CREATE DATABASE COMMAND
DBCmd = New SqlCommand(Query, DBConnect)
'LOAD PARAMS INTO DB COMMAND
Params.ForEach(Sub(p) DBCmd.Parameters.Add(p)) 'LAMBDA EXPRESSION
'CLEAR PARAMS LIST
Params.Clear()
'EXECUTE COMMAND & FILL DATASET
DBDT = New DataTable
DBDA = New SqlDataAdapter(DBCmd)
RecordCount = DBDA.Fill(DBDT)
Catch ex As Exception
'CAPTURE ERROR
Exception = "ExecQuery Error: " & vbNewLine & ex.Message
Finally
'CLOSE CONNECTION
If DBConnect.State = ConnectionState.Open Then DBConnect.Close()
End Try
End Sub
'ADD PARAMS
Public Sub AddParam(Name As String, Value As Object)
Dim NewParam As New SqlParameter(Name, Value)
Params.Add(NewParam)
End Sub
'ERROR CHECKING
Public Function HasException(Optional Report As Boolean = False) As Boolean
If String.IsNullOrEmpty(Exception) Then Return False
If Report = True Then MsgBox(Exception, MsgBoxStyle.Critical, "Exception:")
Return True
End Function
End Class

SQLControl class is the brain child of VBToolbox user. It is a good tool, once created, just simply use it and not have to worry about all complicated setups that normally associated with SQL data connection. Besides, it makes the solution much cleaner & simpler.
There is no need to modify SQLControl class, when you need to update the changes, simply:
'SAVE UPDATES TO DATABASE
SQL.DBDA.UpdateCommand = New SqlClient.SqlCommandBuilder(SQL.DBDA).GetUpdateCommand 'Need primary key in SEL statement
SQL.DBDA.Update(SQL.DBDT)
and refresh the DataGridview afterwards.

The issue appears to be as I suspected it was. Here's for code from the form:
SQL.ExecQuery("SELECT * FROM dtbRateVerse;")
SQL.DBDA.UpdateCommand = New SqlClient.SqlCommandBuilder(SQL.DBDA).GetUpdateCommand
SQL.DBDA.Update(SQL.DBDT)
In that, you first call ExeQuery and finally call Update on the data adapter and pass the DBDT DataTable. In your ExecQuery method, you have this:
DBDT = New DataTable
DBDA = New SqlDataAdapter(DBCmd)
RecordCount = DBDA.Fill(DBDT)
That means that the first code snippet is going to be calling Update and passing a DataTable that was just freshly created and populated. Why would you expect that DataTable to have any changes in it to save? This is an example of why I think DAL classes like this are garbage.
If you're going to be using a class like that then you should be creating a command builder inside it when you create the data adapter, e.g.
'DB DATA
Public DBDA As SqlDataAdapter
Public DBCB As SqlCommandBuilder
Public DBDT As DataTable
and:
'EXECUTE COMMAND & FILL DATASET
DBDT = New DataTable
DBDA = New SqlDataAdapter(DBCmd)
DBCB = New SqlCommandBuilder(DBDA)
RecordCount = DBDA.Fill(DBDT)
Now there's no need to call ExecQuery again or create your own command builder. Just call ExecQuery once when you want the data, get the populated DataTable, use it and then call Update on the data adapter and pass that DataTable when it's time to save.
That said, you don't even necessarily need to change that class. If you already have an instance and you already called ExecQuery then it already contains the data adapter and the DataTable. You can still create your own command builder if you want but just don't call ExecQuery again and lose the objects you already had. If you changed that first code snippet to this:
Dim commandBuilder As New SqlClient.SqlCommandBuilder(SQL.DBDA)
SQL.DBDA.Update(SQL.DBDT)
Then it would work, assuming that you are using the same SQLControl instance as you used to get the data in the first place.

Related

How to extract data from database access from a form and display on another form as receipt when button print is clicked in vb.net [duplicate]

I've heard that "everyone" is using parameterized SQL queries to protect against SQL injection attacks without having to vailidate every piece of user input.
How do you do this? Do you get this automatically when using stored procedures?
So my understanding this is non-parameterized:
cmdText = String.Format("SELECT foo FROM bar WHERE baz = '{0}'", fuz)
Would this be parameterized?
cmdText = String.Format("EXEC foo_from_baz '{0}'", fuz)
Or do I need to do somethng more extensive like this in order to protect myself from SQL injection?
With command
.Parameters.Count = 1
.Parameters.Item(0).ParameterName = "#baz"
.Parameters.Item(0).Value = fuz
End With
Are there other advantages to using parameterized queries besides the security considerations?
Update: This great article was linked in one of the questions references by Grotok.
http://www.sommarskog.se/dynamic_sql.html
The EXEC example in the question would NOT be parameterized. You need parameterized queries (prepared statements in some circles) to prevent input like this from causing damage:
';DROP TABLE bar;--
Try putting that in your fuz variable (or don't, if you value the bar table). More subtle and damaging queries are possible as well.
Here's an example of how you do parameters with Sql Server:
Public Function GetBarFooByBaz(ByVal Baz As String) As String
Dim sql As String = "SELECT foo FROM bar WHERE baz= #Baz"
Using cn As New SqlConnection("Your connection string here"), _
cmd As New SqlCommand(sql, cn)
cmd.Parameters.Add("#Baz", SqlDbType.VarChar, 50).Value = Baz
Return cmd.ExecuteScalar().ToString()
End Using
End Function
Stored procedures are sometimes credited with preventing SQL injection. However, most of the time you still have to call them using query parameters or they don't help. If you use stored procedures exclusively, then you can turn off permissions for SELECT, UPDATE, ALTER, CREATE, DELETE, etc (just about everything but EXEC) for the application user account and get some protection that way.
Definitely the last one, i.e.
Or do I need to do somethng more extensive ...? (Yes, cmd.Parameters.Add())
Parametrized queries have two main advantages:
Security: It is a good way to avoid SQL Injection vulnerabilities
Performance: If you regularly invoke the same query just with different parameters a parametrized query might allow the database to cache your queries which is a considerable source of performance gain.
Extra: You won't have to worry about date and time formatting issues in your database code. Similarly, if your code will ever run on machines with a non-English locale, you will not have problems with decimal points / decimal commas.
You want to go with your last example as this is the only one that is truly parametrized. Besides security concerns (which are much more prevalent then you might think) it is best to let ADO.NET handle the parametrization as you cannot be sure if the value you are passing in requires single quotes around it or not without inspecting the Type of each parameter.
[Edit] Here is an example:
SqlCommand command = new SqlCommand(
"select foo from bar where baz = #baz",
yourSqlConnection
);
SqlParameter parameter = new SqlParameter();
parameter.ParameterName = "#baz";
parameter.Value = "xyz";
command.Parameters.Add(parameter);
Most people would do this through a server side programming language library, like PHP's PDO or Perl DBI.
For instance, in PDO:
$dbh=pdo_connect(); //you need a connection function, returns a pdo db connection
$sql='insert into squip values(null,?,?)';
$statement=$dbh->prepare($sql);
$data=array('my user supplied data','more stuff');
$statement->execute($data);
if($statement->rowCount()==1){/*it worked*/}
This takes care of escaping your data for database insertion.
One advantage is that you can repeat an insert many times with one prepared statement, gaining a speed advantage.
For instance, in the above query I could prepare the statement once, and then loop over creating the data array from a bunch of data and repeat the ->execute as many times as needed.
Your command text need to be like:
cmdText = "SELECT foo FROM bar WHERE baz = ?"
cmdText = "EXEC foo_from_baz ?"
Then add parameter values. This way ensures that the value con only end up being used as a value, whereas with the other method if variable fuz is set to
"x'; delete from foo where 'a' = 'a"
can you see what might happen?
Here's a short class to start with SQL and you can build from there and add to the class.
MySQL
Public Class mysql
'Connection string for mysql
Public SQLSource As String = "Server=123.456.789.123;userid=someuser;password=somesecurepassword;database=somedefaultdatabase;"
'database connection classes
Private DBcon As New MySqlConnection
Private SQLcmd As MySqlCommand
Public DBDA As New MySqlDataAdapter
Public DBDT As New DataTable
Public BindSource As New BindingSource
' parameters
Public Params As New List(Of MySqlParameter)
' some stats
Public RecordCount As Integer
Public Exception As String
Function ExecScalar(SQLQuery As String) As Long
Dim theID As Long
DBcon.ConnectionString = SQLSource
Try
DBcon.Open()
SQLcmd = New MySqlCommand(SQLQuery, DBcon)
'loads params into the query
Params.ForEach(Sub(p) SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value))
'or like this is also good
'For Each p As MySqlParameter In Params
' SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value)
' Next
' clears params
Params.Clear()
'return the Id of the last insert or result of other query
theID = Convert.ToInt32(SQLcmd.ExecuteScalar())
DBcon.Close()
Catch ex As MySqlException
Exception = ex.Message
theID = -1
Finally
DBcon.Dispose()
End Try
ExecScalar = theID
End Function
Sub ExecQuery(SQLQuery As String)
DBcon.ConnectionString = SQLSource
Try
DBcon.Open()
SQLcmd = New MySqlCommand(SQLQuery, DBcon)
'loads params into the query
Params.ForEach(Sub(p) SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value))
'or like this is also good
'For Each p As MySqlParameter In Params
' SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value)
' Next
' clears params
Params.Clear()
DBDA.SelectCommand = SQLcmd
DBDA.Update(DBDT)
DBDA.Fill(DBDT)
BindSource.DataSource = DBDT ' DBDT will contain your database table with your records
DBcon.Close()
Catch ex As MySqlException
Exception = ex.Message
Finally
DBcon.Dispose()
End Try
End Sub
' add parameters to the list
Public Sub AddParam(Name As String, Value As Object)
Dim NewParam As New MySqlParameter(Name, Value)
Params.Add(NewParam)
End Sub
End Class
MS SQL/Express
Public Class MSSQLDB
' CREATE YOUR DB CONNECTION
'Change the datasource
Public SQLSource As String = "Data Source=someserver\sqlexpress;Integrated Security=True"
Private DBCon As New SqlConnection(SQLSource)
' PREPARE DB COMMAND
Private DBCmd As SqlCommand
' DB DATA
Public DBDA As SqlDataAdapter
Public DBDT As DataTable
' QUERY PARAMETERS
Public Params As New List(Of SqlParameter)
' QUERY STATISTICS
Public RecordCount As Integer
Public Exception As String
Public Sub ExecQuery(Query As String, Optional ByVal RunScalar As Boolean = False, Optional ByRef NewID As Long = -1)
' RESET QUERY STATS
RecordCount = 0
Exception = ""
Dim RunScalar As Boolean = False
Try
' OPEN A CONNECTION
DBCon.Open()
' CREATE DB COMMAND
DBCmd = New SqlCommand(Query, DBCon)
' LOAD PARAMS INTO DB COMMAND
Params.ForEach(Sub(p) DBCmd.Parameters.Add(p))
' CLEAR PARAMS LIST
Params.Clear()
' EXECUTE COMMAND & FILL DATATABLE
If RunScalar = True Then
NewID = DBCmd.ExecuteScalar()
End If
DBDT = New DataTable
DBDA = New SqlDataAdapter(DBCmd)
RecordCount = DBDA.Fill(DBDT)
Catch ex As Exception
Exception = ex.Message
End Try
' CLOSE YOUR CONNECTION
If DBCon.State = ConnectionState.Open Then DBCon.Close()
End Sub
' INCLUDE QUERY & COMMAND PARAMETERS
Public Sub AddParam(Name As String, Value As Object)
Dim NewParam As New SqlParameter(Name, Value)
Params.Add(NewParam)
End Sub
End Class

Executing a query in VB.NET causes GUI bug [duplicate]

This question already has an answer here:
When clicking "btnLogin", my forms decrease in size.
(1 answer)
Closed 4 years ago.
Whenever my program needs too query the database, it'll result in GUI bugs like the form fonts changing randomly, the form itself resizing, the data in the datagrid being mixed together, and it affect the overall program, not just the form that is calling the query function.
I use this connection
"Provider=Microsoft.ACE.OLEDB.12.0;"
Public DBDA As OleDbDataAdapter
Public DBDT As DataTable
DBDT = New DataTable
DBDA = New OleDbDataAdapter(DBCmd)
RecordCount = DBDA.Fill(DBDT)
and here is my Execute Query function:
Public Sub ExecQuery(Query As String)
'RESET QUERY STATS
RecordCount = 0
Exception = ""
Try
'OPEN A CONNECTION
DBCon.Open()
'CREATE DB COMMAND
DbCmd = New OleDbCommand(Query, DBCon)
'LOAD PARAMETERS INTO COMMAND
Params.ForEach(Sub(Par) DbCmd.Parameters.Add(Par))
'CLEAR PARAMETERS LIST
Params.Clear()
'EXECUTE COMMAND & FILL DATA
DBDT = New DataTable
DBDA = New OleDbDataAdapter(DbCmd)
RecordCount = DBDA.Fill(DBDT)
Catch ex As Exception
Exception = ex.Message
End Try
'CLOSE CONNECTION
If DBCon.State = ConnectionState.Open Then DBCon.Close()
End Sub
and an example of how I query from a form
Private QC As New QueryControl
Public Sub RefreshGrid()
'RUN QUERY
QC.ExecQuery("SELECT * FROM Database")
If Not String.IsNullOrEmpty(QC.Exception) Then MsgBox(QC.Exception) : Exit Sub
'POPULATE DATAGRID
Datagridview1.DataSource = QC.DBDT
End Sub
I tried running the debugging the program while commenting each line individually and concluded that the ExecQuery() function is what causes the issue. Anyone met something similar?
I had the problem of the form resizing. It was the 32 bit Ace provider. I downloaded the 64 bit Ace provider and installed it. In the solution properties uncheck the prefer 32 bit box and all should be well.

Custom TableAdapter Delete Method Override

I'm attempting to overload the "Delete" method of a TableAdapter (approach). How can I execute an SQL statement from 'here' to handle the delete?
I've got:
Namespace AFL_BackendDataSetTableAdapters
Partial Class Log_entry_unitTableAdapter
Public Overloads Function Delete(ByVal LogEntryID As Integer) As Integer
Dim SQL As String
SQL = "DELETE FROM log_entry_unit WHERE log_entry_unit_id=" & LogEntryID
'?????.Execute SQL
Return 0
End Function
End Class
End Namespace
The overload is working fine, but I don't know how to do the hard part and actually manipulate the data from here. Previously, I've just gone into the Dataset Designer and manually updated the generated methods to work like I want them, but whenever I use the wizard to regenerate the dataset, that (as expected) gets overwritten.
I've previously only ever manipulated Data using the generated methods, and now I'm stuck.
EDIT w/ Final Answer
Based on William's help below here's the final working solution (Note I just had to use OleDb instead of SQL since my Dataset is Access:
Imports System.Data.OleDb
Namespace AFL_BackendDataSetTableAdapters
Partial Class Log_entry_unitTableAdapter
Public Overloads Function Delete(ByVal LogEntryID As Integer) As Integer
Dim queryString As String = "DELETE FROM log_entry_unit WHERE log_entry_unit_id=" & LogEntryID
Dim command As New OleDbCommand(queryString, Connection)
Dim r As Integer
Try
Connection.Open()
r = command.ExecuteNonQuery()
Catch ex As Exception
MsgBox(ex.Message)
r = 0
Finally
Connection.Close()
End Try
Return r
End Function
End Class
End Namespace
I hardcoded a connection string for reference only. This should be in a config file. As an example:
Dim connectionString As String = _
"Data Source=(local);Initial Catalog=YourDatabase;" _
& "Integrated Security=true"
Dim queryString As String = "DELETE FROM log_entry_unit WHERE log_entry_unit_id=" & LogEntryID
' Create and open the connection in a using block. This
' ensures that all resources will be closed and disposed
' when the code exits.
Using connection As New SqlConnection(connectionString)
' Create the Command
Dim command As New SqlCommand(queryString, connection)
' Open the connection in a try/catch block.
Try
connection.Open()
command.ExecuteNonQuery()
Catch ex As Exception
' handle exception here
End Try
End Using
EDIT
I probably should of mentioned you will probably want to fill your adapter again after the delete.

Update sqlite file from datatable

Aiming to load a table from a db3 (sqlite) file into a datatable. Then load this datatable into a datagridview to allow editing of it. Then I want to save the edited datatable back to the original db3 file - overwriting any changes.
I've got as far as getting the table from the db3 + into the DGV. I wont include any code around the datatable to dgv. Just needing how to write this datable back to the db3 file. How do I do this? Code:
Imports System.Data.SQLite
Public Class DBOps
Public Function ImportGEDb3(Filepath As String) As DataTable
Dim dt As New DataTable("Data")
Dim cnn As New SQLiteConnection("Data Source='" & Filepath & "'")
cnn.Open()
Dim mycommand As New SQLiteCommand(cnn)
mycommand.CommandText = "Select * from Data"
Dim reader As SQLiteDataReader = mycommand.ExecuteReader()
dt.Load(reader)
reader.Close()
cnn.Close()
Return dt
End Function
Public Sub SaveGEDb3(dt as datatable, filepath as string)
' save passed datatable to File above
end sub
end class
The SQLiteDataAdapter class is the simplest choice for this kind of updates. The method Update of the class scans the DataTable passed and for each row that has the RowState property different from DataRowState.Unchanged executes the appropriate INSERT, DELETE, UPDATE command (if the SELECT command extracts from the DataTable the primary key). So assuming that your SELECT * FROM Data returns also the primary key of the table you could change your code to take advantage of the SQLiteDataAdapter functionality
Imports System.Data.SQLite
Public Class DBOps
' Global because you create it in the ImportGEDb3 and use it in the SaveGEDB3 '
Private daImport As SQLiteDataAdapter
Public Function ImportGEDb3(Filepath As String) As DataTable
Dim dt As New DataTable("Data")
daImport = new SQLiteDataAdapter("Select * from Data", "Data Source='" & Filepath & "'")
daImport.Fill(dt)
' This is critical, it is the SQLiteCommandBuilder that takes '
' the SQLiteDataAdapter SELECT statement and builds the required'
' INSERT,UPDATE,DELETE commands.'
Dim builder = new SQLiteCommandBuilder(daImport)
Return dt
End Function
Public Sub SaveGEDb3(dt as datatable)
if daImport IsNot Nothing Then
daImport.Update(dt)
End If
End Sub
End class

Using one dataset in many forms

I am using vb.net to load data from sql server database. I have defined a dataset in my module
and want to use it in many forms Public dsDataset As DataSet. When I load the main form I load the options that are likely to be the same in all the forms like the departments and sections, so that I do not have to load them again in all the forms. When Navigating from main form to an other form I pass this dataset in a the form constructor.
In the main form I am loading these options:
Sub loadOptions()
Dim da As SqlDataAdapter
Dim sql As String
Try
sqlConn = New SqlConnection(connString)
sqlConn.Open()
sql = " select depId, name from DEPARTMENT "
da = New SqlDataAdapter(sql, sqlConn)
da.Fill(dsDataset, "department")
sql = " select select depId, sectionId, name from SECTION "
da = New SqlDataAdapter(sql, sqlConn)
da.Fill(dsDataset, "section")
'----------------------------------------------------------------------
sqlConn.Close()
Catch ex As Exception
sqlConn.Close()
MsgBox(Err.Description)
End Try
End Sub
Then lets say I have printers form where I load data from from the database and use the option in the dsDataset instead of loading them again. I pass the dataset as follows:
Dim printers As frmPrinters = New frmPrinters(dsDataset)
printers.ShowDialog()
and in the printers form I have a constructor as follows:
Sub New(ByRef dsDataset As DataSet)
InitializeComponent()
cmbDepartment.DataSource = dsDataset.Tables("department")
cmbDepartment.DisplayMember = "name"
cmbDepartment.ValueMember = "depId"
cmbSection.DataSource = dsDataset.Tables("section")
cmbSection.DisplayMember = "name"
cmbSection.ValueMember = "sectionId"
End Sub
and load all the printers to the dataset with no problem:
sql = "select * from printer where printerId=" & printerId
daPrinter = New SqlDataAdapter(sql, sqlConn)
daPrinter.Fill(dsDataset, "printer")
dgvPrinters.DataSource = dsDataset.Tables("printers")
Now when I double click any of the printers I go an other form to load the details of that particular printer as follows:
printerId = dgvPrinters.Rows(dgvPrinters.CurrentRow.Index).Cells(0).Value
printerMode = "modify"
Dim printer As New frmPrinter(dsDataset)
printer.ShowDialog()
In the new form I use the same constructor above but the problem happens there. The following error comes:
Value cannot be null.
Parameter name: dataSet
What I am doing wrong?
It doesn't look like you are saving the dsDataset anywhere in your constructor. For example in a private variable. My guess is that inside frmPrinters you will have a public dsDataSet variable which you never set to anything.
So what you should do is the following:
Create a private dsDataSet variable in frmPrinter called _dsDataset:
Private _dsDataset As DataSet
In the constructor do this:
_dsDataset = dsDataset
And finally in the doubleclick event send the private variable:
Dim printer As New frmPrinter(_dsDataset)
And then you should be fine.