so i tried learning visual studio (c#) and run to this problem that i genuinely can't get my way to fix this problem. Inserting data from visual studio form to database
my control
Imports System.Data.Odbc
Public Class ClsCtlPenumpang
Dim SQL As String
Public Function SIMPAN_DATAPenumpang(ByVal _pbl As ClsEntInfoPenumpang) As String
Dim KP As String
KP = ""
TUTUPKONESI()
With _pbl
Sql = "CALL INSERTPENUMPANG('" & .Nama_penumpang & "')"
MsgBox(Sql)
Try
DTA = New OdbcDataAdapter(Sql, BUKAKONEKSI)
DTS = New DataSet
DTA.Fill(DTS, "TABEL_KDPenumpang")
KP = DTS.Tables("TABEL_KDPenumpang").Rows(0)(0).ToString
Catch ex As Exception
If _pbl Is Nothing Then
_pbl = New ClsEntInfoPenumpang
End If
End Try
End With
TUTUPKONESI()
Return KP
End Function
Function kodebaru() As String
Dim baru As String
Dim kodeakhir As Integer
Try
DTA = New OdbcDataAdapter("select max(right(id_penumpang,2))from info_penumpang", BUKAKONEKSI)
DTS = New DataSet()
DTA.Fill(DTS, "max_kode")
kodeakhir = Val(DTS.Tables("max_kode").Rows(0).Item(0))
baru = "SEAT" & Strings.Right("0" & kodeakhir + 1, 2)
Return baru
Catch ex As Exception
Throw New Exception(ex.Message)
End Try
End Function
End Class
my entities
Public Class ClsEntInfoPenumpang
Private _id_penumpang As String
Private _nama_penumpang As String
Public Property Id_penumpang As String
Get
Return _id_penumpang
End Get
Set(value As String)
_id_penumpang = value
End Set
End Property
Public Property Nama_penumpang As String
Get
Return _nama_penumpang
End Get
Set(value As String)
_nama_penumpang = value
End Set
End Property
End Class
my form
Public Class FormPenumpang
txtNama.Focus()
txtIDPenumpang.Text = KontrolPenumpang.kodebaru()
txtIDPenumpang.Enabled = False
End Sub
Private Sub btnSave_Click_1(sender As Object, e As EventArgs) Handles btnSave.Click
With EntitasPenumpang
.Id_penumpang = txtIDPenumpang.Text
.Nama_penumpang = txtNama.Text
End With
KontrolPenumpang.SIMPAN_DATAPenumpang(EntitasPenumpang)
MdiParent = FormUtama
End Sub
End Class
the problem is in the control, can't add anything while the automatic numbering is working fine
(yes my odbc is right, i can add/delete using other form, and the database i assume is correct)
You are totally misusing that data adapter. You don't call Fill to insert data. Fill is to execute a SELECT statement and retrieve data into a DataTable. If you want to use a data adapter to insert data into a database using a stored procedure then you need:
A DataTable containing one or more DataRows with a RowState of Added.
A data adapter with its InsertCommand set to a command containing the name of the stored procedure in its CommandText, its CommandType set to StoredProcedure and appropriate its Parameters populated appropriately.
To call Update on the data adapter and pass the DataTable as an argument.
Generally speaking, you use the same data adapter to retrieve data into a DataTable and then save changes from that DataTable back to the database. Here's one I prepared earlier:
Private connection As New SqlConnection("connection string here")
Private adapter As New SqlDataAdapter("SELECT ID, Name, Quantity, Unit FROM StockItem", _
connection)
Private table As New DataTable
Private Sub InitialiseDataAdapter()
Dim delete As New SqlCommand("DELETE FROM StockItem WHERE ID = #ID", Me.connection)
Dim insert As New SqlCommand("INSERT INTO StockItem (Name, Quantity, Unit) VALUES (#Name, #Quantity, #Unit)", Me.connection)
Dim update As New SqlCommand("UPDATE StockItem SET Name = #Name, Quantity = #Quantity, Unit = #Unit WHERE ID = #ID", Me.connection)
delete.Parameters.Add("#ID", SqlDbType.Int, 4, "ID")
insert.Parameters.Add("#Name", SqlDbType.VarChar, 100, "Name")
insert.Parameters.Add("#Quantity", SqlDbType.Float, 8, "Quantity")
insert.Parameters.Add("#Unit", SqlDbType.VarChar, 10, "Unit")
update.Parameters.Add("#Name", SqlDbType.VarChar, 100, "Name")
update.Parameters.Add("#Quantity", SqlDbType.Float, 8, "Quantity")
update.Parameters.Add("#Unit", SqlDbType.VarChar, 10, "Unit")
update.Parameters.Add("#ID", SqlDbType.Int, 4, "ID")
Me.adapter.DeleteCommand = delete
Me.adapter.InsertCommand = insert
Me.adapter.UpdateCommand = update
Me.adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey
End Sub
Private Sub GetData()
'Retrieve the data.
Me.adapter.Fill(Me.table)
'The table can be used here to display and edit the data.
'That will most likely involve data-binding but that is not a data access issue.
End Sub
Private Sub SaveData()
'Save the changes.
Me.adapter.Update(Me.table)
End Sub
That example uses SqlClient to connect to SQL Server, which you should do if that's the database you're using. There are dedicated ADO.NET providers for various other databases too, which you should generally use in preference to ODBC. This example also uses inline SQL. As I said, if you are using a stored procedure, replace the SQL code with the stored procedure name - JUST the name - and then set the CommandType of the command to System.Data.CommandType.StoredProcedure.
This question was hard to decipher but I think the problem is:
Sql = "CALL INSERTPENUMPANG('" & .Nama_penumpang & "')"
i genuinely can't get my way to fix this problem. Inserting data from visual studio winform to database
You're trying to insert data to the database and you're trying to call a Stored Procedure named INSERTPENUMPANG.
In Sql Server the way to execute Stored Procedures is EXEC
Solution:
Sql = "EXEC INSERTPENUMPANG('" & .Nama_penumpang & "')"
Related
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.
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
Private Function GetSvcType(ByVal oCommand As OleDbCommand, ByVal SSTGroupID As Integer) As DataTable
Dim sSQL As New StringBuilder
sSQL.AppendLine(" Select SSTServiceTypeID AS ID, SSTServiceTypeName AS Name ")
sSQL.AppendLine(" from fgen_SSTServiceType (nolock) ")
sSQL.AppendLine(" Where 1=1 AND Disabled = 0 ")
sSQL.AppendLine(" AND fgen_SSTServiceType.SSTGroupID = #SSTGroupID ")
oCommand.Parameters.AddWithValue("#SSTGroupID", SSTGroupID)
Return GetDataTable(sSQL.ToString)
End Function
Private Function GetDataTable(ByVal SQL As String) As DataTable
Dim oConn As OleDbConnection = New OleDbConnection(_strConnection)
Dim oCommand As New OleDbCommand("", oConn)
oCommand.Connection.Open()
oCommand.CommandText = SQL
oCommand.Parameters.Clear()
Dim oDataTable As New DataTable
Dim oDataAdapter As New OleDbDataAdapter(oCommand)
oDataAdapter.Fill(oDataTable)
If oDataTable.Rows.Count > 0 Then
GetDataTable = oDataTable
Else
GetDataTable = Nothing
End If
oCommand.Connection.Close()
oCommand.Dispose()
End Function
I've been searching for hours on end and can't seem to find a solution. I need your help please thanks
I've updated my question include the GetDataTable function. Please take a look thanks.
Your command never gets the text from the StringBuilder. So I think the missing link is that you should assign the string you've built to the command text
oCommand.CommandText = sSQL.ToString()
then add the parameter after that
Private Function GetSvcType(ByVal oCommand As OleDbCommand, ByVal SSTGroupID As Integer) As DataTable
Dim sSQL As New StringBuilder()
sSQL.AppendLine(" Select SSTServiceTypeID AS ID, SSTServiceTypeName AS Name ")
sSQL.AppendLine(" from fgen_SSTServiceType (nolock) ")
sSQL.AppendLine(" Where 1=1 AND Disabled = 0 ")
sSQL.AppendLine(" AND fgen_SSTServiceType.SSTGroupID = #SSTGroupID ")
oCommand.CommandText = sSQL.ToString()
oCommand.Parameters.AddWithValue("#SSTGroupID", SSTGroupID)
Return GetDataTable(oCommand.CommandText)
End Function
Alternatively, you may want to use a Using to create a command and dispose it. I'd write it but I don't see your connection so you should look into this answer for an example.
I wasn't sure what the Where 1 = 1 and the (no lock) were doing so I removed them.
The function FillDataTable contains all your database access code which keeps it separate from the User Interface code. Your database objects should be locale so you can control that they are closed and disposed. The Using...End Using block takes care of this even if there is an error. Get rid of any class level variables for commands and connections. Both the command and connection are included; note the comma at the end if the first line of the Using.
You can pass your connection string directly to the constructor of the connection and pass the command text and connection directly to the constructor of the command. Saves having to set these properties individually.
OleDb pays no attention to the name of the parameter, so, the order that the parameter is added to the Parameters collection must match the order that the parameter appears in the command text. In this case, you have only one but just for future reference. It is better to use the Parameters.Add() which includes the database datatype. See http://www.dbdelta.com/addwithvalue-is-evil/
and
https://blogs.msmvps.com/jcoehoorn/blog/2014/05/12/can-we-stop-using-addwithvalue-already/
and another one:
https://dba.stackexchange.com/questions/195937/addwithvalue-performance-and-plan-cache-implications
Here is another
https://andrevdm.blogspot.com/2010/12/parameterised-queriesdont-use.html
Note: I had to guess at the datatype of your parameter. Check your database for the actual type.
Always open your connection at the last possible moment (the line before the .Execute...) and close it as soon as possible (the End Using)
Private Function FillDataTable(GroupID As Long) As DataTable
Dim strSQL = "Select SSTServiceTypeID AS ID, SSTServiceTypeName As Name
From fgen_SSTServiceType
Where Disabled = 0
And SSTGroupID = #SSTGroupID "
Dim dt As New DataTable
Using cn As New OleDbConnection("Your connection string"),
cmd As New OleDbCommand(strSQL, cn)
cmd.Parameters.Add("#SSTGroupID", OleDbType.BigInt).Value = GroupID
cn.Open()
dt.Load(cmd.ExecuteReader)
End Using
Return dt
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim dt = FillDataTable(7L) 'the L indicates that this is a long,pass the GroupID to the function
DataGridView1.DataSource = dt
End Sub
EDIT
Dim dt = FillDataTable(7L) 'In the button code
And in the Data Access code change Oledb to Sql
Imports System.Data.SqlClient
Class DataAccess
Private Function FillDataTable(GroupID As Long) As DataTable
Dim strSQL = "Select SSTServiceTypeID AS ID, SSTServiceTypeName As Name
From fgen_SSTServiceType
Where Disabled = 0
And SSTGroupID = #SSTGroupID "
Dim dt As New DataTable
Using cn As New SqlConnection("Your connection string"),
cmd As New SqlCommand(strSQL, cn)
cmd.Parameters.Add("#SSTGroupID", SqlDbType.BigInt).Value = GroupID
cn.Open()
dt.Load(cmd.ExecuteReader)
End Using
Return dt
End Function
End Class
Can anyone please look at below code and advise what is wrong in this?
I was trying to insert values into an MS Access database. Compiler throws no error but the values are not inserted in table.
Code:
Private Sub btnSignInOK_Click(sender As Object, e As EventArgs) Handles btnSignInOK.Click
uniqid = "1"
Try
Dim ConnSignIn As New OleDb.OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\Resources\DBpPNRGENERATORDATA.accdb;Persist Security Info=True")
Dim CmdSignIn As New OleDb.OleDbCommand
If Not ConnSignIn.State = ConnectionState.Open Then
ConnSignIn.Open()
End If
CmdSignIn.Connection = ConnSignIn
CmdSignIn.CommandText = "DELETE TEMPSIGNIN.* FROM TEMPSIGNIN WHERE IDENTIFIER='" + uniqid + "'"
CmdSignIn.ExecuteNonQuery()
CmdSignIn.CommandText = "INSERT INTO TEMPSIGNIN(IDENTIFIER,EPR,Partition,Host)VALUES('" & uniqid & "','" & tbSigninEPR.Text & "','" & cbSignInPartition.Text & "','" & tbSignInAl.Text & "')"
CmdSignIn.ExecuteNonQuery()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
The DELETE command is not quite right. You do not need to specify the columns in a command as you are deleting the row not the columns:
DELETE FROM [TableName]
Next you should be using parameters when executing SQL commands. This is to reduce syntax issues but more importantly stops SQL injection. See Bobby Tables for more details on this. I use the ? placeholder within my SQL command when using parameters. I also specify the data type so consider using the OleDbParameter Constructor (String, OleDbType) to add your parameters.
I would also consider implementing Using:
Managed resources are disposed of by the .NET Framework garbage collector (GC) without any extra coding on your part. You do not need a Using block for managed resources. However, you can still use a Using block to force the disposal of a managed resource instead of waiting for the garbage collector.
You could implement a check for the value returned by ExecuteNonQuery() to see if the row was deleted before inserting the new row.
All together your code would look something like this:
uniqid = "1"
Dim rowDeleted As Boolean
Using con As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\Resources\DBpPNRGENERATORDATA.accdb;Persist Security Info=True")
Using cmd As New OleDbCommand("DELETE FROM [TEMPSIGNIN] WHERE [IDENTIFIER] = ?", con)
con.Open()
cmd.Parameters.Add("#Id", OleDbType.Integer).Value = uniqid
rowDeleted = cmd.ExecuteNonQuery() = 1
End Using
If rowDeleted Then
Using cmd As New OleDbCommand("INSERT INTO [TEMPSIGNIN] ([IDENTIFIER], [EPR], [Partition], [Host]) VALUES (?, ?, ?, ?)", con)
cmd.Parameters.Add("#Id", OleDbType.[Type]).Value = uniqid
cmd.Parameters.Add("#EPR", OleDbType.[Type]).Value = tbSigninEPR.Text
cmd.Parameters.Add("#Partition", OleDbType.[Type]).Value = cbSignInPartition.Text
cmd.Parameters.Add("#Host", OleDbType.[Type]).Value = tbSignInAl.Text
cmd.ExecuteNonQuery()
End Using
End If
End Using
Note that I have used OleDbType.[Type]. You will want to replace [Type] with the data type you've used on your database.
As mentioned above, your syntax for the DELETE SQL is wrong. So when the database tries to execute it, it fails, so the INSERT never runs. Change the DELETE to:
CmdSignIn.CommandText = "DELETE FROM TEMPSIGNIN WHERE IDENTIFIER='" + uniqid + "'"
Also, you should learn how to use SQL Parameters and not use string concatenation to create a SQL string. Using parameters your DELETE query becomes something like (this is not 100%, it's off the top of my head)
CmdSignIn.CommandText = "DELETE FROM TEMPSIGNIN WHERE IDENTIFIER=#ID"
CmdSignIn.Parameters.Add("#ID", uniqid)
CmdSignIn.ExecuteNonQuery()
Okay, focusing on why there is no exception thrown no matter the syntax used. Are you sure you are targeting the right physical database?
If targeting the correct database, if the database is in Solution Explorer then select the database, select properties, check the property "Copy to Output Directory" the default is Copy Always, if you have this set then on each build the database in the project folder overwrite the database in the app folder directory thus no changes would be there.
Side note:
Going with the recommendations provided so far, here is a pattern to consider. Place all database operations in it's own class. The class below is simple, read, update, add and remove from a MS-Access database table Customers with (for this sample) a primary key, company name and contact name.
Each operation in it's own function with error handling which on failure you can get back the exception (I simply use the exception message for this sample).
A BindingSource is used as this component makes life easier when traversing data bound controls such as TextBoxes and or a DataGridView.
The form code displays data in a DataGridView, there is a button to remove the current record and a button to add a new record (I left out assertion to see if the TextBoxes have data). I used two TextBox controls to get information for inserting records.
Form code
Public Class StackOverFlowForm1
Private Operations As New Sample2
Private bsCustomers As New BindingSource
Private Sub StackOverFlowForm1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim ops As New Sample2
If ops.LoadCustomers Then
bsCustomers.DataSource = ops.CustomersDataTable
DataGridView1.DataSource = bsCustomers
Else
MessageBox.Show($"Failed to load table data{Environment.NewLine}{ops.Exception.Message}")
End If
End Sub
Private Sub cmdDeleteCurrent_Click(sender As Object, e As EventArgs) Handles cmdDeleteCurrent.Click
If bsCustomers.Current IsNot Nothing Then
Dim ops As New Sample2
Dim currentIdentifier As Integer = CType(bsCustomers.Current, DataRowView).Row.Field(Of Integer)("Identifier")
If Not ops.DeleteCustomer(currentIdentifier) Then
MessageBox.Show($"Failed to remove customer: {ops.Exception.Message}")
Else
bsCustomers.RemoveCurrent()
End If
End If
End Sub
Private Sub cmdInsert_Click(sender As Object, e As EventArgs) Handles cmdInsert.Click
Dim ops As New Sample2
Dim newId As Integer = 0
If ops.AddNewRow(txtCompanyName.Text, txtContact.Text, newId) Then
CType(bsCustomers.DataSource, DataTable).Rows.Add(New Object() {newId, txtCompanyName.Text, txtContact.Text})
Else
MessageBox.Show($"Failed to add customer: {ops.Exception.Message}")
End If
End Sub
End Class
Then we have a class for database operations. The database is located in the app folder.
Public Class Sample2
Private Builder As New OleDbConnectionStringBuilder With
{
.Provider = "Microsoft.ACE.OLEDB.12.0",
.DataSource = IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Database1.accdb")
}
Private mExceptiom As Exception
''' <summary>
''' Each method when executed, if there is an exception thrown
''' then mException is set and can be read back via Exception property
''' only when a method returns false.
''' </summary>
''' <returns></returns>
Public ReadOnly Property Exception As Exception
Get
Return mExceptiom
End Get
End Property
''' <summary>
''' Container for data read in from a database table
''' </summary>
''' <returns></returns>
Public Property CustomersDataTable As DataTable
Public Function LoadCustomers() As Boolean
If Not IO.File.Exists(Builder.DataSource) Then
Return False
End If
Try
CustomersDataTable = New DataTable
Using cn As New OleDbConnection With {.ConnectionString = Builder.ConnectionString}
Using cmd As New OleDbCommand With {.Connection = cn}
cmd.CommandText = "SELECT Identifier, CompanyName, ContactTitle FROM Customers"
cn.Open()
CustomersDataTable.Load(cmd.ExecuteReader)
CustomersDataTable.DefaultView.Sort = "CompanyName"
CustomersDataTable.Columns("Identifier").ColumnMapping = MappingType.Hidden
End Using
End Using
Return True
Catch ex As Exception
mExceptiom = ex
Return False
End Try
End Function
''' <summary>
''' Delete a customer by their primary key
''' </summary>
''' <param name="CustomerId"></param>
''' <returns></returns>
Public Function DeleteCustomer(ByVal CustomerId As Integer) As Boolean
Dim Success As Boolean = True
Dim Affected As Integer = 0
Try
Using cn As New OleDbConnection With {.ConnectionString = Builder.ConnectionString}
Using cmd As New OleDbCommand With {.Connection = cn}
cmd.CommandText = "DELETE FROM Customers WHERE Identifier = #Identifier"
cmd.Parameters.AddWithValue("#Identifier", CustomerId)
cn.Open()
Affected = cmd.ExecuteNonQuery()
If Affected = 1 Then
Success = True
End If
End Using
End Using
Catch ex As Exception
Success = False
mExceptiom = ex
End Try
Return Success
End Function
Public Function UpdateCustomer(ByVal CustomerId As Integer, ByVal CompanyName As String, ByVal ContactName As String) As Boolean
Dim Success As Boolean = True
Dim Affected As Integer = 0
Try
Using cn As New OleDbConnection With {.ConnectionString = Builder.ConnectionString}
Using cmd As New OleDbCommand With {.Connection = cn}
cmd.CommandText = "UPDATE Customer SET CompanyName = #CompanyName, ContactName = #ContactName WHERE Identifier = #Identifier"
cmd.Parameters.AddWithValue("#CompanyName", CompanyName)
cmd.Parameters.AddWithValue("#ContactName", ContactName)
cmd.Parameters.AddWithValue("#Identifier", ContactName)
cn.Open()
Affected = cmd.ExecuteNonQuery()
If Affected = 1 Then
Success = True
End If
End Using
End Using
Catch ex As Exception
Success = False
mExceptiom = ex
End Try
Return Success
End Function
''' <summary>
''' Add new row, if successful provide the new record's primary key
''' </summary>
''' <param name="Name"></param>
''' <param name="ContactName"></param>
''' <param name="Identfier"></param>
''' <returns></returns>
Public Function AddNewRow(ByVal Name As String, ByVal ContactName As String, ByRef Identfier As Integer) As Boolean
Dim Success As Boolean = True
Try
Using cn As New OleDbConnection With {.ConnectionString = Builder.ConnectionString}
Using cmd As New OleDbCommand With {.Connection = cn}
cmd.CommandText = "INSERT INTO Customers (CompanyName,ContactName) Values(#CompanyName,#ContactName)"
cmd.Parameters.AddWithValue("#CompanyName", Name)
cmd.Parameters.AddWithValue("#ContactName", ContactName)
cn.Open()
Dim Affected As Integer = cmd.ExecuteNonQuery()
If Affected = 1 Then
cmd.CommandText = "Select ##Identity"
Identfier = CInt(cmd.ExecuteScalar)
End If
End Using
End Using
Catch ex As Exception
Success = False
mExceptiom = ex
End Try
Return Success
End Function
End Class
This is my first post in here, but this forum already helped me a lot.
First, sorry for my English, i'm from Brazil and i'm trying to write without a translator.
I'm developing a software for a supermarket, but i'm having problems with the connection to the database. I'm trying to make all the connections and transactions programmatically (DataSets, BindingSources and so).
I've already managed to connect with SQL Server Express 2008, using a Function ("consulta") inside a Module ("db"):
Dim ad As SqlDataAdapter = New SqlDataAdapter
Function consulta(ByVal tabela As String, Optional opt As Boolean = False, Optional optparam As String = "") As DataSet
Dim ds As New DataSet
Try
Dim connstring As String = "Data Source=NOTEBOOK\SQLEXPRESS;Initial Catalog=SysMarket;Persist Security Info=True;User ID=admin;Password=XXXXXX"
Dim conObj As New SqlConnection(connstring)
Dim sql As String
If opt = True Then
sql = "SELECT * FROM " & tabela & " " & optparam
Else
sql = "SELECT * FROM " & tabela
End If
Dim cmd As SqlCommand = New SqlCommand(sql, conObj)
ad.SelectCommand = cmd
conObj.Open()
ad.Fill(ds, tabela)
ad.Dispose()
cmd.Dispose()
conObj.Close()
Return ds
Catch ex As Exception
MessageBox.Show("Erro na consulta" & vbCrLf & ex.InnerException.ToString, "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error)
ds.Clear()
Return ds
End Try
End Function
And this is a part of the main code where I make a SelectQuery and put into a BindingSource:
Dim ds As DataSet = db.consulta("departamentos")
Private Sub cad_departamento_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BindingSource1.DataSource = ds
BindingSource1.DataMember = "departamentos"
TextBox1.DataBindings.Add("Text", BindingSource1, "id")
TextBox2.DataBindings.Add("Text", BindingSource1, "departamento")
End Sub
But my problem is when I have to Update the database, by adding, editing or deleting some item from BindingSource. Because in the Module I've closed the connection to the SQL Server. So I will need reopen this connection and then, somehow "read" the DataSet with the change and Update the database?
Someone could explain this to me or show me a example?
Thank you.
You will use a data adapter to save the data, just as you used one to retrieve the data. You will have to create an InsertCommand if you want to insert new records, an UpdateCommand if you want to update existing records and a DeleteCommand if you want to delete existing records. You can write those yourself or, if the conditions are right, you can use a command builder to do it for you.
If your query is based on a single table and you want to insert/update all the columns you retrieve back to that same table then a SqlCommandBuilder may be your best bet. You simply pass in the query and the command builder will use it to generate the action commands. That gives you limited flexibility but if you're just doing single-table operations then you don't need that added flexibility.
Such a method might look something like this:
Public Sub SaveChanges(tableName As String, data As DataSet)
Dim query = "SELECT * FROM " & tableName
Using adapter As New SqlDataAdapter(query, "connection string here")
Dim builder As New SqlCommandBuilder(adapter)
adapter.Update(data, tableName)
End Using
End Sub
I did what you said, but when I open the Form again, the new data are not there.
I made some changes in the code, perhaps because it did not work
Private Sub btnSave_Click(sender As Object, e As EventArgs) Handles btnSave.Click
BindingSource1.EndEdit()
ds.AcceptChanges()
db.SaveChanges("departamentos", "INSERT INTO departamentos VALUES('', " & TextBox2.Text & ")", ds)
ds = db.consulta("departamentos")
End Sub
And the code in the Module
Function SaveChanges(tableName As String, query As String, data As DataSet)
Using adapter As New SqlDataAdapter(query, "Data Source=NOTEBOOK\SQLEXPRESS;Initial Catalog=SysMarket;Persist Security Info=True;User ID=admin;Password=XXXXX")
Dim builder As New SqlCommandBuilder(adapter)
adapter.Update(data, tableName)
Return True
End Using
End Function