VB.NET MySqlCommand Stored Procedure Parameter Order? - mysql

I create a dictionary in a function which I pass to the function executing the sproc.
Dim PParamDict As New Dictionary(Of String, String)
PParamDict.Add("sname", name)
PParamDict.Add("sdescription", description)
PParamDict.Add("sLoggedInID", LoggedInID)
PParamDict.Add("sCompanyID", CompanyID)
The dictionary gets passed to the function PParamDict -> ParameterDict
Dim dbComm As New MySqlCommand(ProcedureName, PConnection)
Dim pair As KeyValuePair(Of String, String)
For Each pair In ParameterDict
dbComm.Parameters.AddWithValue(pair.Key, pair.Value)
Next
Parameters are added from the dictionary.
This is a straightforward sproc, nothing special about it, a simple insert.
CREATE PROCEDURE `NewCollection`(
IN `sLoggedInID` INT(5),
IN `sCompanyID` INT(5),
IN `sname` VARCHAR(20),
IN `sdescription` VARCHAR(500))
BEGIN
INSERT INTO `collection`
(`userid`, `companyid`, `name`, `description`, `generated`)
VALUES
(sLoggedInID, sCompanyID, sname, sdescription, CURRENT_TIMESTAMP);
END
This works as long as the PParamDict.Add statements are in that order. If they're in a different order they get passed as they come in. This is the most ridiculous thing I've ever seen, I'm passing the damn keys to the MySqlCommand which are defined letter for letter in the sproc. I must be missing something, please help!

I have a similar problem, however my list of parameter values has come via reflection so I don't build a list manually and have no control of the order.
In Pete's answer, the binding of the parameters will always be in the correct order because it is not a stored procedure, but a text command where the parameter names are used as place holders so it won't matter which order they are added.
To get around the ordering problem I simply explicity call the procedure as text (like Pete's INSERT) rather than use command.CommandType = System.Data.CommandType.StoredProcedure like this:
command.CommandText = "call procname (#param1, #param2, #param3 ... );"
Then I can add my parameters in any order i want
command.Parameters.AddWithValue("#param3", 123)
command.Parameters.AddWithValue("#param2", 456)
command.Parameters.AddWithValue("#param1", 789)
Hope this helps.
EDIT: This method won't work if you have output parameters

Perhaps this will help. A StringDictionary "implements a hash table with the key and the value strongly typed to be strings rather than objects".
A HashTable "represents a collection of key/value pairs that are organized based on the hash code of the key".
As you add pairs to your StringDictionary, it gets reorganized by the hash code of the key string.
If you build a SqlParameterCollection instead of a StringDictionary, your parameters are named and a for each iterator should match the parameters in your sproc nicely.
UPDATE
Adding code example.
Private Function GetParameters(ByVal name As String, ByVal description As String, ByVal LoggedInID As Integer, ByVal CompanyID As Integer) As SqlParameterCollection
Dim cmd As SqlCommand = New SqlCommand()
Dim pc As SqlParameterCollection = cmd.Parameters 'SqlParameterCollection constructor is marked as "Friend" so it has to be instantiated this way.'
pc.AddWithValue("sname", name)
pc.AddWithValue("sdescription", description)
pc.AddWithValue("sLoggedInID", LoggedInID)
pc.AddWithValue("sCompanyID", CompanyID)
Return pc
End Function
Private Sub ExecuteStoredProcedure(ByVal pc As SqlParameterCollection)
Dim sp As String = String.Empty
Dim conn As SqlConnection = Nothing
Dim cmd As SqlCommand = Nothing
Dim da As SqlDataAdapter = Nothing
Dim ds As DataSet = Nothing
Dim p As SqlParameter = Nothing
Try
sp = "INSERT INTO `collection` (`user_id`, `company_id`, `name`, `description`, `generated`) VALUES (`sLoggedInID`, `sCompanyID`, `sname`, `sdescription`, CURRENT_TIMESTAMP)"
conn = New SqlConnection("your connection string here")
cmd = New SqlCommand(sp, conn)
For Each p In pc
cmd.Parameters.Add(p)
Next
da = New SqlDataAdapter(cmd)
ds = New DataSet
da.Fill(ds)
Catch ex As SqlException
'handle exception'
Catch ex As Exception
'handle exception'
Finally
If conn IsNot Nothing Then
conn.Dispose()
End If
If cmd IsNot Nothing Then
cmd.Dispose()
End If
If da IsNot Nothing Then
da.Dispose()
End If
End Try
End Sub

Related

Input string was not in a correct format message error during update record vb.net and mysql

I have MySQL Database and VB.Net project.
I have created a sub to execute any SQL statement and it's working well.
Public Sub Me_Sub_GetUpdate(ByVal SqlStr As String, ByVal xPar() As MySqlParameter)
Try
xCMD = New MySqlCommand(SqlStr, Conn)
xCMD.CommandType = CommandType.Text
If xPar IsNot Nothing Then
For i As Integer = 0 To xPar.Length - 1
xCMD.Parameters.Add(xPar(i))
Next
End If
If Conn.State = ConnectionState.Open Then Conn.Close()
Conn.Open()
xCMD.ExecuteNonQuery()
Conn.Close()
xCMD.Dispose()
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
when I use the next subroutine to update a record:
Try
Dim SqlStr As String
Dim xParam As MySqlParameter() = New MySqlParameter(1) {}
xParam(0) = New MySqlParameter("#ID", SqlDbType.TinyInt)
xParam(0).Value = 1
xParam(1) = New MySqlParameter("#TheName1", SqlDbType.NVarChar)
xParam(1).Value = Trim(Me.t1.Text)
SqlStr = "UPDATE tblcominfo Set TheName1=#TheName1 Where ID = #ID"
xCLS.Me_Sub_GetUpdate(SqlStr, xParam)
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
I got a message (Input string was not in a correct format)!!
When I deleted the parameters and run the update code with direct values it's working!!
I don't know what's the problem, can you help me?
The problem is that you're using SqlDbType, not MySqlDbType. SqlDbType is used by the SQL Server provider. Since you're using MySQL, you need to use MySqlDbType.
The constructor you're calling is not the one you expected:
new MySqlParameter("#TheName1", SqlDbType.NVarChar)
...is calling the constructor that takes (name As String, value As Object) because there's no overload that takes SqlDbType. You're then overwriting the value, and the MySqlDbType property is whatever the default is (probably MySqlDbType.Decimal).
On the other hand, if you pass a MySqlDbType value:
new MySqlParameter("#TheName1", MySqlDbType.VarChar)
...it will call the constructor that takes (name As String, dbType As MySqlDbType) and the MySqlDbType property will be initialized from it.

Parameterize SQL Queries

I want parameterize some SQL Statements so my code isn't vunerable to SQL Injections any longer But i have actually no plan how to parameterize for example a where clause.
Dim accID As String = DatabaseConnecter.readField("SELECT ID FROM accounts WHERE accountname ='" & user & "' AND password='" & pw & "';")
The Problem is if you type in a given username, for example test and extend the username with. You can log in without entering the password into the Application.
Edit:
Public Function readField(ByVal sql As String) As String
Dim output As String = "ERROR"
Using cn = New MySqlConnection(connString.ToString())
Using cmd = New MySqlCommand(sql, cn)
cn.Open()
Using rd = cmd.ExecuteReader()
Try
rd.Read()
output = rd.GetString(0)
rd.Close()
Catch ex As Exception
End Try
End Using
cn.Close()
End Using
End Using
Return output
End Function
ยดยดยด
To have a parameterized query you need to create parameters and write a proper SQL text where, in place of values typed directly from your user, you have parameter placeholders.
So, for example, you sql text should be something like this
Dim sqlText = "SELECT ID FROM accounts WHERE accountname =#name AND password=#pwd"
Now you have a parameterized text, but stil we need to create the parameters that will be sent to the database engine together with your sql command.
You can create the parameter (two in this case) in this way before calling the method that executes the query
Dim p1 as MySqlParameter = new MySqlParameter("#name", MySqlDbType.VarChar)
p1.Value = user
Dim p2 as MySqlParameter = new MySqlParameter("#pwd", MySqlDbType.VarChar)
p2.Value = password
Dim pms As List(Of MySqlParameter) = new List(Of MySqlParameter)()
pms.Add(p1)
pms.Add(p2)
Now we need to pass this list to your method (and this requires changes to your method signature)
DatabaseConnecter.readField(sqlText, pms)
The method itself should change to something like
Public Function readField(ByVal sql As String, Optional pms As List(Of MySqlParameter) = Nothing) As String
Dim output As String = "ERROR"
Using cn = New MySqlConnection(connString.ToString())
Using cmd = New MySqlCommand(sql, cn)
cn.Open()
' This block adds the parameter defined by the caller to the command
' The parameters are optional so we need to check if we have really received the list or not
if pms IsNot Nothing Then
cmd.Parameters.AddRange(pms.ToArray())
End If
Using rd = cmd.ExecuteReader()
Try
rd.Read()
output = rd.GetString(0)
rd.Close()
Catch ex As Exception
End Try
End Using
' no need to close when inside a using block
' cn.Close()
End Using
End Using
Return output
End Function
The method now has an optional parameter that will contain the list of the parameters required by the query (or nothing if your query doesn't require parameters). This list is added to the command parameters collection and the query is now executed.
Final Note: Storing passwords in clear text into a database is a well known security problem. I suggest you to search about how to store passwords in a database.
Private Function GetID(User As String, pw As String) As String
Using cmd As New SqlCommand("SELECT ID FROM accounts WHERE accountname =#user AND password=#password", New SqlConnection(SQLConnString))
cmd.Parameters.AddWithValue("#user", User)
cmd.Parameters.Add("#password", SqlDbType.NVarChar)
cmd.Parameters("#password").Value = pw
Try
cmd.Connection.Open()
Return cmd.ExecuteScalar()
Catch ex As Exception
'handle error
Return Nothing
Finally
cmd.Connection.Close()
End Try
End Using
End Function
I've demostrated two methods of setting the parameters. Search for more info or comparison.

How to read a value from mysql database?

I want to be able to read a value (in this case an Group ID). All the topics and tutorials I've watched/read take the data and put it into a textbox.
I don't want to put it in a textbox in this case; I want to grab the Group ID and then say:
If Group ID = 4 then login
Here is an image of the database.
Basically, but none of the tutorials I watch or the multiple forums. None of them take a a value and say if value = 4 then login or do something else.
If text = "1" Then
MysqlConn = New MySqlConnection
MysqlConn.ConnectionString =
"server='ip of server'.; username=; password=; database="
Dim READER As MySqlDataReader
Dim member_group_id As String
Try
MysqlConn.Open()
Dim Query As String
Query = "SELECT * FROM `core_members` where name='" & TextBox2.Text & "'"
Query = "SELECT * FROM `nexus_licensekeys` where lkey_key='" & TextBox1.Text & "'"
COMMAND = New MySqlCommand(Query, MysqlConn)
READER = COMMAND.ExecuteReader
Dim count As Integer
count = 0
While READER.Read
count = count + 1
End While
Here is what I have so far. I'm kind of new implementing mysql data with visual basic and only recently started to get into it. I'm not sure what comes next or how to even start with reading the group id etc.
As I said any help from here on out would be highly appreciated of how to read the group id and say if this group id = this number then do this or that. I'm sure you get the idea.
I divided the code into UI Sub, and Data Access Function that can return data to the UI. Your Event procedure code should be rather brief and the functions should have a single purpose.
Keep your database objects local to the method. This way you can have better control. The Using...End Using blocks ensure that your database objects are closed and disposed even if there is an error.
I leave it to you to add validation code. Checking for empty TextBox or no return of records.
I hope this serves as a quick introduction to using ADO.net. The take away is:
Use Parameters
Make sure connections are closed. (Using blocks)
Private ConnString As String = "server=ip of server; username=; password=; database="
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim GroupID As String = GetGroupID(TextBox1.Text)
If GroupID = "4" Then
'your code here
End If
Dim LocalTable As DataTable = GetLicenseKeysData(TextBox1.Text)
'Get the count
Dim RowCount As Integer = LocalTable.Rows.Count
'Display the data
DataGridView1.DataSource = LocalTable
End Sub
Private Function GetGroupID(InputName As String) As String
'Are you sure member_group_id is a String? Sure looks like it should be an Integer
Dim member_group_id As String = ""
'You can pass the connection string directly to the constructor of the connection
Using MysqlConn As New MySqlConnection(ConnString)
'If you only need the value of one field Select just the field not *
'ALWAYS use parameters. See comment by #djv concerning drop table
Using cmd As New MySqlCommand("SELECT g_id FROM core_members where name= #Name")
'The parameters are interperted by the server as a value and not executable code
'so even if a malicious user entered "drop table" it would not be executed.
cmd.Parameters.Add("#Name", MySqlDbType.VarChar).Value = InputName
MysqlConn.Open()
'ExecuteScalar returns the first column of the first row of the result set
member_group_id = cmd.ExecuteScalar.ToString
End Using
End Using
Return member_group_id
End Function
Private Function GetLicenseKeysData(InputName As String) As DataTable
Dim dt As New DataTable
Using cn As New MySqlConnection(ConnString)
Using cmd As New MySqlCommand("SELECT * FROM `nexus_licensekeys` where lkey_key= #Name;", cn)
cmd.Parameters.Add("#Name", MySqlDbType.VarChar).Value = InputName
cn.Open()
dt.Load(cmd.ExecuteReader())
End Using
End Using
Return dt
End Function

Updating date in SQL/VB/Asp.Net

I'm pretty much a novice at all this. I know bits. Just trying to store a date in an SQL database. I've set it to 06/06/2015 temporarily in code below to see if I can get it to update but it updates it as 01/01/0001. When I suss it, The value I actually want to store is todays date plus 6 months. EG: if its 31/07/2015 today, I want it to store 31/01/2016. Can anyone help ? Much appreciated...
ASPX.VB
Protected Sub imgBtnDatechange_Click(sender As Object, e As ImageClickEventArgs) Handles imgBtn.Click
Dim acc As New accounts(Membership.GetUser().ProviderUserKey)
Dim adjustedDate as Date = "06/06/2015"
acc.UpdateVipEndDate(acc.accountID, acc.adjustedDate)
End Sub
ACCOUNTS.VB
Public Property adjustedDate As Date
Public Sub UpdateVipEndDate(ByVal accountID As Guid, ByVal adjustedDate As Date)
Dim DBConnect As New DBConn
Using db As DbConnection = DBConnect.Conn("DBConnectionString")
Dim cmd As SqlCommand = DBConnect.Command(db, "UpdateVipEndDate")
cmd.Parameters.Add(New SqlParameter("accountID", SqlDbType.UniqueIdentifier, ParameterDirection.Input)).Value = accountID
cmd.Parameters.Add(New SqlParameter("newadjustedDate", SqlDbType.Date, ParameterDirection.Input)).Value = adjustedDate
db.Open()
cmd.ExecuteNonQuery()
cmd.Dispose()
cmd = Nothing
db.Dispose()
db.Close()
End Using
End Sub
STORED PROCEDURE
CREATE PROCEDURE [UpdateVipEndDate]
#accountID uniqueidentifier,
#newadjustedDate date
AS
BEGIN
UPDATE tblAccounts SET [vipEndDate] = #newadjustedDate WHERE [accountID] = #accountID
END
You set a date here:
Dim adjustedDate as Date = "06/06/2015"
But you never use that variable anywhere. Instead, you're using a parameter on the acc object:
acc.UpdateVipEndDate(acc.accountID, acc.adjustedDate)
So, presumably, the acc.adjustedDate value is otherwise empty or some default MinDate value.
It seems like you're confusing a few things here...
If something is a Date, use it as a Date. Not as a String.
If the UpdateVipEndDate method is on the acc object, why do you need to pass it references to its own parameters? It should be able to access those values internally in the method.
I'm probably getting off point here, though. The simplest thing, it seems, would be to not use a local variable and use the object member that you use elsewhere:
acc.adjustedDate = "06/06/2015"

Checking which row from DataGridView exists in MySQL takes too long

I'm executing a query which returns me around 400 records (orders) and I display them in DataGridView. Then I have to check each row and make a green every row which exists in other MySQL database (by ID). I think I'm not doing this optimally. Here is what I am doing:
For Each oRow As DataGridViewRow In dgv.Rows
Dim orderNumber As Integer = oRow.Cells(0).Value
Dim exist As Boolean = mySql.CheckIfExists(shop, orderNumber)
If Not exist Then
Continue For
End If
If exist Then
oRow.DefaultCellStyle.BackColor = Color.LightGreen
oRow.DefaultCellStyle.SelectionBackColor = Color.Green
oRow.DefaultCellStyle.SelectionForeColor = Color.LightCoral
Continue For
End If
Next
Here is CheckIfExists() method in MySQL class:
Public Function CheckIfExists(ByVal shop As String, ByVal orderNumber As Integer) As Boolean
Dim dt As New DataTable
Dim sql As String = "" 'sql query'
Connect(mSOH) 'msoh is a connection string from My.Settings'
Dim sqlCommand As New MySqlCommand
With sqlCommand
.Connection = connection
.CommandText = sql
End With
Try
Dim sqlReader As MySqlDataReader = sqlCommand.ExecuteReader()
While sqlReader.Read()
Return True
End While
Catch ex As MySqlException
Logi.LogInfo(ex)
Catch ex As Exception
Logi.LogInfo(ex)
Finally
Disconnect()
End Try
Return False
End Function
And Connect and Disconnect methods if they are important:
Private Sub Connect(shop As String)
Select Case shop
Case "jablotron"
connection = New MySqlConnection(csJablotron)
Case "bcs"
connection = New MySqlConnection(csBCS)
Case "mSOH"
connection = New MySqlConnection(csmSOH)
Case Else
connection = New MySqlConnection(shop)
End Select
Try
connection.Open()
Catch ex As MySqlException
Logi.LogInfo(ex)
Catch ex As Exception
Logi.LogInfo(ex)
End Try
End Sub
Private Sub Disconnect()
Try
connection.Dispose()
Catch ex As MySqlException
Logi.LogInfo(ex)
Catch ex As Exception
Logi.LogInfo(ex)
End Try
End Sub
So if I try to check each row by this way it takes some time (around <1 second if database is on localhost, and around 30 second if database is on remote server and I try to connect via VPN). Is this way being optimal - checking each row like this? Is it a good approach? Please give me some tip and advice :) I know that code is in VB.NET, but guys from C# can also help me :)
Use this logic: Pre-load your order numbers into lists and cache them. For how long? - this is something for you to decide, based on how volatile the data is. Then check against those lists.
As well, It will be faster if you just parse your grid data by database and build In statements for each of your databases and load all needed records at once. Then only loop through already loaded data. And only relative data will be loaded.
Dim existingOrders as New List(of String)
Dim shopOrders as New List(of String)
/* Collect orders into list */
For Each oRow As DataGridViewRow In dgv.Rows
Dim orderNumber As Integer = oRow.Cells(0).Value
shopOrders.Add(orderNumber)
Next
/* Get and accumulate existing orders from each shop */
For Each shop as string In shops
GetOrdersByShop(shop, shopOrders, existingOrders)
Next
For Each oRow As DataGridViewRow In dgv.Rows
If existingOrders.Contains(oRow.Cells(0).Value) Then
oRow.DefaultCellStyle.BackColor = Color.LightGreen
oRow.DefaultCellStyle.SelectionBackColor = Color.Green
oRow.DefaultCellStyle.SelectionForeColor = Color.LightCoral
End If
Next
/* ---------------------------------------------------- */
Private Sub GetOrdersByShop(shop As string, shopOrders As List(of String),
existingOrders as New List(of String))
/* here, turn shopOrders into In('ord1', 'ord2', . . . )
and after running query add found orders to existingOrders */
End Sub
You can also use dictionary - it will make search work faster than list and you may store which order is in which shop, etc.
First off, a DataGridView is a VIEW object. You should use it as such. For data, you use a data object.
Personally I would do something like this:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim dtMain = SqlReadDatatable(firstConnString, "SELECT * FROM SomeTable")
Dim lstGreenIds = (From row In SqlReadDatatable(secondConnString, "SELECT ID FROM SomeOtherTable")
Select CInt(row("ID"))).ToList
dtMain.Columns.Add(New DataColumn("Exists", GetType(Boolean)))
For Each dtRow As DataRow In (From row In dtMain.Rows Where lstGreenIds.Contains(CInt(row("ID"))))
dtRow("Exists") = True
Next
DataGridView1.DataSource = dtMain
End Sub
Private Function SqlReadDatatable(ByVal connStr As String, ByVal query As String) As DataTable
Dim dt As New DataTable
Using conn As New SqlConnection(connStr) : conn.Open()
Using cmd As New SqlCommand(query, conn)
dt.Load(cmd.ExecuteReader)
End Using
End Using
Return dt
End Function
Private Sub DataGridView1_RowPostPaint(sender As Object, e As DataGridViewRowPostPaintEventArgs) Handles DataGridView1.RowPostPaint
Dim dgv = CType(sender, DataGridView)
Dim dgvRow = dgv.Rows(e.RowIndex)
Dim dtRow = CType(dgvRow.DataBoundItem, DataRow)
If dtRow("Exists") Then
dgvRow.DefaultCellStyle.BackColor = Color.LightGreen
dgvRow.DefaultCellStyle.SelectionBackColor = Color.Green
dgvRow.DefaultCellStyle.SelectionForeColor = Color.LightCoral
End If
End Sub
This cleanly separates data from visualisation. The color is only applied when the row comes into view, no need to iterate over the whole bunch at once.
EDIT: I should also note that the cleanest way to do this would be server-side. In MySQL you can use cross-database queries if both databases are on the same server. If they are on different servers, you can create a FEDERATED TABLE on one of the servers and connect it to the remote server. You can read about those here: http://dev.mysql.com/doc/refman/5.0/en/federated-storage-engine.html . In the case of two databases on the same server, you could just do a join:
SELECT f.*, NOT ISNULL(s.OrderNum) AS Exists
FROM 'firstdb'.'firsttable' f
LEFT JOIN 'seconddb'.'secondtable' s ON f.OrderNum = s.OrderNum
And then use the Exists column in code to color the row.