Redefining/Re-setting parameters in MySQL query - mysql

I have the following code for inserting data into a table using a MySQL query in VB.NET
Dim MySqlCmdStr = "INSERT INTO tb_idlink(id1,id2) " &
"VALUES (#par1,#par2)"
MySqlCmd.CommandText = MySqlCmdStr
Dim checkedItem As Object
For Each checkedItem In CheckedListBox_1.CheckedItems
Try
MySqlCmd.Connection = MySqlConn
With MySqlCmd
.Parameters.AddWithValue("#par1", currentID)
.Parameters.AddWithValue("#par2", checkedItem.ToString())
End With
MySqlConn.Open()
MySqlCmd.ExecuteNonQuery()
MySqlConn.Close()
Catch ex As MySqlException
MessageBox.Show(ex.Message)
End Try
Next
My problem is if I have more than one box checked in CheckedListBox_1 then on the second loop an exception that says something like "parameter #par1 already defined". Is there a way I can re-define it? I'm not entirely familiar with the whole API.
Also, I'm not 100% sure if looping it is the best way to do this, but it's the first thing that popped into my head. Feel free to suggest an alternative way of doing this.

You dont redefine the parameters, you just supply a new value:
Dim SQL = "INSERT INTO tb_idlink (id1,id2) VALUES (#par1,#par2)"
Using dbcon As New MySqlConnection(MySQLConnStr)
Using cmd As New MySqlCommand(SQL, dbcon)
' define the parameter names and types
cmd.Parameters.Add("#par1", MySqlDbType.Int32)
cmd.Parameters.Add("#par2", MySqlDbType.Int32) ' ????
dbcon.Open()
For Each checkedItem In CheckedListBox1.CheckedItems
' provide the parameter values
cmd.Parameters("#par1").Value = currentID
cmd.Parameters("#par2").Value = Convert.ToInt32(checkedItem)
cmd.ExecuteNonQuery()
Next
End Using
End Using
Your code appears to reuse a global connection, that is ill-advised. The above uses Using blocks to create, use and and dispose of the DbConnection and DbCommand objects in the smallest scope possible
You should favor Add rather than AddWithValue so you can specify the datatype rather than forcing the the DB Provider guess and reduce the chance of data type mismatch errors.
These datatypes are a guess; CurrentId is not defined anywhere and given the names, both seem to be integers not strings.

Related

an error keeping popping up in my codes (connection already open)

This error keeps popping up!!! An unhandled exception of type 'System.InvalidOperationException' occurred in MySql.Data.dll
Additional information: The connection is already open.
Dim cmd As MySqlCommand
con.Open()
Try
cmd = con.CreateCommand()
cmd.CommandText = "update animal_sale set #NOAB,#Amount,#Tax,#Total where Species=#Species"
cmd.Parameters.AddWithValue("#Species", TextBoxSpecies.Text)
cmd.Parameters.AddWithValue("#NOAB", TextBoxNo.Text)
cmd.Parameters.AddWithValue("#Amount", TextBoxAmount.Text)
cmd.Parameters.AddWithValue("#Tax", TextBoxTax.Text)
cmd.Parameters.AddWithValue("#Total", TextBoxTotal.Text)
cmd.ExecuteNonQuery()
load()
Catch ex As Exception
End Try
End Sub
It looks like you are not closing the connection after executing the query. You only have
con.Open()
and are not closing the connection after
cmd.ExecuteNonQuery()
Keep your database objects local to the method where they are used. Then you always know the state of a connection and can be sure they are closed and disposed. Using...End Using blocks do this for you even if there is an error. In this code both the connection and the command are covered by a single Using block. Note the comma at the end of the first Using line.
You can pass your connection string directly to the constructor of the connection.
You can pass your command text and the connection directly to the constructor of the command.
You Update sql command is not correct. You need to tell the server what fields to update. I had to guess at the names of the fields. Check you database for the correct names and adjust the code accordingly.
Please don't use .AddWithValue. 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
I had to guess at the datatypes and field size for the .Add method. Check you database for the correct values and adjust the code.
I converted the text box strings to the proper datatype here in the database code but normally these values would be parsed and converted before they reach this code.
Private Sub UpdateSale()
Using con As New MySqlConnection("Your connection string"),
cmd As New MySqlCommand("update animal_sale set nonab = #NOAB, amount = #Amount, tax = #Tax, total = #Total where species = #Species;", con)
cmd.Parameters.Add("#Species", MySqlDbType.VarChar, 100).Value = TextBoxSpecies.Text
cmd.Parameters.Add("#NOAB", MySqlDbType.Int32).Value = CInt(TextBoxNo.Text)
cmd.Parameters.Add("#Amount", MySqlDbType.Decimal).Value = CDec(TextBoxAmount.Text)
cmd.Parameters.Add("#Tax", MySqlDbType.Decimal).Value = CDec(TextBoxTax.Text)
cmd.Parameters.Add("#Total", MySqlDbType.Decimal).Value = CDec(TextBoxTotal.Text)
con.Open
cmd.ExecuteNonQuery()
End Using
End Sub

Using 'Else' in While data.Read() does not work?

What I want to happen is if, textbox3.Text does not equal data(0) value then I want the MsgBox("test") to trigger. However, it does not. If the value of textbox3 does not exist with data(0) I want MsgBox("test") to trigger. I've tried every variation I could think of and I cannot get it to work.
Right now, if textbox.Text does not equal data(0) value nothing happens. However, if textbox3.Text equals data(0) then both Label3.Text = data(1) and MsgBox("Join code has been applied.") work.
Dim conn As New MySqlConnection
conn.ConnectionString = "server=;userid=;password=;database="
conn.Open()
Dim sqlquery As String = "SELECT * FROM joincodes WHERE code = '" & TextBox3.Text & "';"
Dim data As MySqlDataReader
Dim adapter As New MySqlDataAdapter
Dim command As New MySqlCommand
command.CommandText = sqlquery
command.Connection = conn
adapter.SelectCommand = command
data = command.ExecuteReader
While data.Read()
If data.HasRows() = True Then
If TextBox3.Text = data(0) Then
Label3.Text = data(1)
MsgBox("Join code has been applied.")
Else
MsgBox("test")
End If
End If
End While
There are a few things that need to be changed in the code.
Database connections have "unmanaged resources" associated with them, which means that you have to .Dispose() of them when you have finished using them. To avoid some fiddly code, VB.NET conveniently provides the Using statement.
It is best to give controls meaningful names because it is much easier to see what is going on in the code. E.g. if you accidentally typed TextBox37 when you meant TextBox87 it would be hard to see, but you wouldn't mistype tbUserName for tbFavouriteColour.
In MySQL, CODE is a keyword, so you need to escape it with backticks to be safe: MySQL Keywords and Reserved Words
Putting variables directly into SQL statements is generally a mistake. SQL parameters are used for doing that; they are easy to use and prevent a lot of problems.
If you are relying on the order of the columns from a database query (e.g. data(0)) then you must specify that order in the query (e.g. SELECT `col1`, `col2` FROM joincodes) because if you use * then they could be returned in any order.
You are probably only interested in the first returned value from the database (if there is a returned value), so I added the ORDER BY `col1` LIMIT 1.
Always use Option Strict On. It will save you time.
With regard to the question as asked, all you need to do is have a flag, I used a boolean variable named success, to indicate if things went right.
I also added some points indicated with 'TODO: in the following code which you'll need to take care of to make sure it works properly:
Option Infer On
Option Strict On
Imports MySql.Data.MySqlClient
' ... (other code) ... '
'TODO: Any type conversion from the string TextBox3.Text.'
'TODO: Give TextBox3 a meaningful name.'
Dim userCode = TextBox3.Text
Dim connStr = "your connection string"
Using conn As New MySqlConnection(connStr)
'TODO: Use the correct column names.'
Dim sql = "SELECT `col1`, `col2` FROM `joincodes` WHERE `code` = #code ORDER BY `col1` LIMIT 1"
Using sqlCmd As New MySqlCommand(sql, conn)
'TODO: Use correct MySqlDbType and change .Size if applicable.'
sqlCmd.Parameters.Add(New MySqlParameter With {.ParameterName = "#code", .MySqlDbType = MySqlDbType.String, .Size = 24, .Value = userCode})
Dim success = False
Dim rdr = sqlCmd.ExecuteReader()
If rdr.HasRows Then
rdr.Read()
'TODO: Change GetString to the appropriate Get<whatever>.'
If rdr.GetString(0) = userCode Then
success = True
'TODO: Check that `col2` is a string - change the GetString call as required and call .ToString() on the result if needed.'
Label3.Text = rdr.GetString(1)
MessageBox.Show("Join code has been applied.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information)
End If
End If
If Not success Then
MsgBox("test")
End If
End Using
End Using

How to execute two separate queries from one DBCommand object?

I have the following code that tries to get records from two different tables and then add them to the particular comboboxes. Only the first query works and the second one is ignored.
Try
sqlConn = New MySqlConnection
connStr = New String("Server = localhost; Database = gen_database; Uid = root; Pwd =")
sqlConn.ConnectionString = connStr
myCommand = New MySqlCommand("Select DevCompanyName from developer_name_table; Select DevType from development_type_table")
myCommand.CommandType = CommandType.Text
myCommand.Connection = sqlConn
ComboBox1.Items.Clear()
sqlConn.Open()
MsgBox("Connection Open.")
dR = myCommand.ExecuteReader()
Do While dR.Read()
ComboBox1.Items.Add(dR("DevCompanyName"))
ComboBox2.Items.Add(dR("DevType")) 'Error shows here Could not find specified column in results: DevType
Loop
Catch ex As MySqlException
MsgBox(ex.ToString)
Finally
dR.Close()
sqlConn.Close()
End Try
I can think of another way which is to do it in multiple query but can the code be simplified to something like this?
Using a DBDataReader with 2 queries, only only the first executes because there is no way to signal which table/query each read item is from. Your "double read" loop seems to assume they will be returned at the same time (rather than one query after the other - the same way they are sent to the DBCOmmand); if it did work that way, it would fail whenever there are not the same number of rows in each table.
Using DataTables affords you the chance to simply bind the result to your combos rather than copying data into them:
Dim SQL = "SELECT * FROM Sample; SELECT * FROM Simple"
Dim ds As New DataSet
Using dbcon As New MySqlConnection(MySQLConnStr),
cmd As New MySqlCommand(SQL, dbcon)
dbcon.Open()
Dim da As New MySqlDataAdapter(cmd)
da.Fill(ds)
End Using
' debug results
Console.WriteLine(ds.Tables.Count)
Console.WriteLine(ds.Tables(0).Rows.Count)
Console.WriteLine(ds.Tables(1).Rows.Count)
If I look at the output window, it will print 2 (tables), 10000 (rows in T(0)) and 6 (rows in T(1)). Not all DBProviders have this capability. Access for instance will choke on the SQL string. Other changes to the way your code is composed:
DBConnections and DBCommand objects need to be disposed. They allocate resources, so they need to be created, used and disposed to release those resources.
The Using block does that for us: The target objects are created at the start and closed and disposed at End Using.
The code above stacks or combines 2 such blocks.
The code fills a DataSet from the query, in this case creating 2 tables. Rather than copying the data from one container to another (like a control), you can use a DataTable as the DataSource:
cboDevName.DataSource = ds.Tables(0)
cboDevName.DisplayMember = "DevName" ' column names
cboDevName.ValueMember = "Id"
cboDevType.DataSource = ds.Tables(1)
cboDevType.DisplayMember = "DevType"
cboDevType.ValueMember = "DevCode"
The result will be all the rows from each table appearing in the respective combo control. Typically with this type of thing, you would want the ID/PK and the name which is meaningful to the user in the query. The user sees the friendly name (DisplayMember), which the code can easily access the unique identifier for the selection (ValueMember).
When using bound list controls, rather than using SelectedIndex and the SelectedIndexChanged event, you'd use SelectedValue and SelectedItem to access the actual data.
MSDN: Using Statement (Visual Basic)
You can use .net connector (download here)
Then you can install it and add it to your project (project -> references -> add -> browse).
Finally add import:
Imports MySql.Data.MySqlClient
So you'll be able to use this:
Dim connStr as String = "Server = localhost; Database = gen_database; Uid = root; Pwd ="
Dim SqlStr as String = "Select DevCompanyName from developer_name_table; Select DevType from development_type_table"
Dim ds As DataSet = MySqlHelper.ExecuteDataset(CnStr, SqlStr)
ds will contain two datatables: one for each query

How to pass CommandText to another MySqlCommand?

The title is a bit furviant, I'll try to explain better. So in my application I've two connection string, one for the local database, and another for the web database. This two database must be updated with the same records. Now in my app when I add a record in the local database I execute a function that pass the MySqlCommand object to another function that use another connection string for the web database. In this second function I need to execute the same operation already performed in the local database. Example code:
Function local database
Dim query = "INSERT INTO text_app (name, last_name)
VALUES(#namep, #last_namep)"
Dim MySqlCommand = New MySqlCommand(query, dbCon)
MySqlCommand.Parameters.AddWithValue("#namep", name.Text)
MySqlCommand.Parameters.AddWithValue("#last_namep", last_name.Text)
MySqlCommand.ExecuteNonQuery()
Sync.SyncOut(MySqlCommand) 'Pass the object to another function
Function web database (SyncOut)
Using dbCon As MySqlConnection = establishWebConnection()
Try
dbCon.Open()
Dim MySqlCommand = New MySqlCommand(query_command, dbCon)
MySqlCommand.ExecuteNonQuery()
Return True
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Using
Now query_command contain the MySqlCommand passed from the local function, dbCon is the new connection object.
When I perform the .ExecuteNonQuery on the SyncOut function I get this error:
Invalid Cast from 'MySqlCommand' type to 'String' type.
What I need is take the .CommandText property contained in the query_command, but I can't access to this property.
What exactly am I doing wrong? How I can achieve this?
The first thing to do is change the name of the variable that represent the MySqlCommand. I can't find any plausible reason to allow a confusion as this to spread along your code. Do not name a variable with the same name of its class even if the language permits, it is very confusing
Dim query = "INSERT INTO text_app (name, last_name)
VALUES(#namep, #last_namep)"
Dim cmd = New MySqlCommand(query, dbCon)
and, of course, change every reference to the old name with the new one.
Now in the declaration of SyncOut write
Public Sub SyncOunt(ByVal cmd As MySqlCommand)
...
' Do not forget to open the connection '
dbCon.Open()
Dim aDifferentCommand = cmd.Clone()
aDifferentCommand.Connection = dbCon
....

Execute multiple stored procedures in VB.NET

I'm creating a WinService in VB.NET to get some data from a Table, do some things with these data and then upload new data into this Table.
What I need is something like this:
Dim conn As New MySqlConnection(my_connString)
conn.Open()
Dim cmd As New MySqlCommand("my_Stored_Procedure_1", conn)
cmd.CommandType = CommandType.StoredProcedure
Dim reader As MySqlDataReader = cmd.ExecuteReader()
While reader.Read()
Try
' SP to SELECT Data from DB table '
Dim columnData As String
columnData = reader("ColumnName")
columnData_2 = reader("ColumnName_2")
' (...) Do something with this Data '
Try
' SP to UPDATE Data into the same DB table '
'cmd.Dispose() '
cmd = New MySqlCommand("my_Stored_Procedure_2", conn)
cmd.CommandType = CommandType.StoredProcedure
cmd.ExecuteReader()
' (...) Do something else '
Catch ex As Exception
Console.WriteLine("ERROR: " & ex.Message)
End Try
Catch ex As Exception
Console.WriteLine("ERROR: " & ex.Message)
End Try
End While
reader.Close()
conn.Close()
The problem is that this doesn't work. It says There is already an open DataReader associated with this Connection which must be closed first. So I tried to create different SQL commands, close and re-open the connection, and create different connections like suggested here but all of them without success. This class seems to be useful but that's a lot of code for a simple (?) task. I've read a lot of similar questions but I didn't found what I need yet.
How can I handle this issue? Some help would be nice.
This sure looks like a duplicate of the question you linked to, but the answer there doesn't provide a lot of detail on how to fix the error. As the error says, you can only have one open reader per connection, so you need to use a different connection for the update. You say you have tried that, but perhaps your attempt was incorrect. As suggested in the linked question, you should also use Using statements for resource management.
So, you probably want something like this (untested, of course!):
Try
Using conn1 As New MySqlConnection(my_connString),
conn2 As New MySqlConnection(my_connString)
conn1.Open()
conn2.Open()
Using cmd1 As New MySqlCommand("my_Stored_Procedure_1", conn1)
cmd1.CommandType = CommandType.StoredProcedure
Using reader1 As MySqlDataReader = cmd1.ExecuteReader()
While reader1.Read()
' SP to SELECT Data from DB table '
Dim columnData As String
columnData = reader1("ColumnName")
columnData_2 = reader1("ColumnName_2")
' (...) Do something with this Data '
' SP to UPDATE Data into the same DB table '
Using cmd2 As New MySqlCommand("my_Stored_Procedure_2", conn2)
cmd2.CommandType = CommandType.StoredProcedure
Using reader2 As MySqlDataReader = cmd2.ExecuteReader()
' (...) Do something else '
End Using ' reader2
End Using ' cmd2
End While
End Using ' reader1
End Using ' cmd1
End Using ' conn1, conn2
Catch ex As Exception
Console.WriteLine("ERROR: " & ex.Message)
End Try
As you can see from the levels of nesting, there is quite a lot going on here in terms of the resource scopes, so you may want to refactor this into multiple methods. You could also use a data adapter to populate a DataTable for the results of my_Stored_Procedure_1, instead of using a DataReader, and then just need a single connection (assuming the data isn't too large for that).