Combination of INSERT and SELECT statement causes error in VB - sql

Here's my SQL Server stored procedure
ALTER PROC [dbo].[insRequestVote]
#fkRequest int,
#fkOrganisation int,
#fkUser int
AS
IF NOT Exists(SELECT 1 FROM [dbo].[tblRequestVote]
WHERE [fkRequest] = #fkRequest
AND [fkOrganisation] = #fkOrganisation
AND [fkUser] = #fkUser)
BEGIN
/* This user from this organisation has not yet voted for this request */
INSERT INTO [dbo].[tblRequestVote] ([fkRequest], [fkOrganisation],[fkUser], [DateStamp])
VALUES (#fkRequest, #fkOrganisation, #fkUser, GetDate());
SELECT
'Inserted' AS VoteResult;
END
ELSE
SELECT 'You have already voted for this SR' AS VoteResult;
When I run this in T-SQL, it works perfectly, e.g.
insRequestVote 1, 4, 23
will return the required phrase. However, when I call the stored procedure from VB.NET, it only writes the record to the table, but doesn't return the phrase.
Code behind:
Dim ADOConn As New ADODB.Connection
ADOConn.ConnectionString = WFConnectionString
If (ADOConn.State <> ConnectionState.Open) Then ADOConn.Open()
Dim ADORecSet As New ADODB.Recordset
Dim sSql As String = ""
Try
'----// Save the Vote for the SR
sSql = "dbo.insRequestVote " & row.Cells(0).Text & "," & row.Cells(1).Text & "," & row.Cells(2).Text
ADORecSet = ADOConn.Execute(sSql)
If Not ADORecSet.EOF Then
If ADORecSet.Fields("VoteResult").Value = "Inserted" Then
gridSRs.DataBind()
row.Cells(4).Text = "1"
End If
End If
ADORecSet.Close()
Catch GenEx As Exception
'----- Catch-all
LogAction(Session("WhoAmI"), GenEx.Message, "Error")
Finally
ADOConn.Close()
ADOConn = Nothing
End Try
This all works fine up to the "If Not ADORecSet.EOF Then" statement, where it jumps to the exception line. The exception message is
Operation is not allowed when the object is closed.
My question is: why does the specific pattern work in all cases except where I have the combination of an INSERT and a SELECT in one stored procedure?

Where do we start... Your code is riddled with anti patterns. You should read up on proper use of ado, our switch to something simpler like an ORM. That aside:
NEVER dynamically build a string to execute in SQL from user inputs. Always, always, always use parameterized queries. Otherwise I can drop all of your db tables from your text box and it is trivial to do.
Since this is a stored procedure you you should set the command type to that and not use text. This can otherwise cause oddities with return value, and it's bad practice.
You tag this as vb. Net but you are using legacy ADO? Use ADO. Net which returns datasets, not long deprecated recordsets

Got it! Just in case someone else is trying this, here is my modified code:
Dim SQLConn As New SqlConnection(WFConnectionStringNET)
If (SQLConn.State <> ConnectionState.Open) Then SQLConn.Open()
Dim sSql As String = ""
Try
'----// Save the Vote for the SR
sSql = "dbo.insRequestVote " & Mid(row.Cells(0).Text, 2, InStr(row.Cells(0).Text, ":") - 2) & "," &
Session("ThisUserOrganisationID") & "," &
Session("ThisUserID")
Dim sqlCmd As New SqlCommand(sSql, SQLConn)
sqlCmd.Parameters.Add("#fkRequest", SqlDbType.Int).Value = Mid(row.Cells(0).Text, 2, InStr(row.Cells(0).Text, ":") - 2)
sqlCmd.Parameters.Add("#fkOrganisation", SqlDbType.Int).Value = Session("ThisUserOrganisationID")
sqlCmd.Parameters.Add("#fkUser", SqlDbType.Int).Value = Session("ThisUserID")
Dim sqlDR As SqlDataReader = sqlCmd.ExecuteReader()
While sqlDR.Read()
If sqlDR("VoteResult") = "Inserted" Then
gridSRs.DataBind()
row.Cells(4).Text = "1"
End If
End While
sqlDR.Close()
LogAction(Session("WhoAmI"), "Voted for SR " & row.Cells(5).Text)
Catch GenEx As Exception
'-----// Catch-all
LogAction(Session("WhoAmI"), GenEx.Message, "Error")
Finally
SQLConn.Close()
SQLConn = Nothing
End Try

Related

Performance issue with SQLite database with VB.NET

I am Inserting the data-table into SQLite Database. I am doing like this.
First I Fetch the data with getdata function and insert it into datatable, then with For Each Loop i made the Insert Command and Execute It. I am having 50000 Records it will take 30 Minutes to run.
Please Guide the suitable approach. Here is the Code.
Dim xtable As DataTable = getdata("select * from tablename")
Dim str As String = Nothing
For Each r As DataRow In xtable.Rows ''''HERE IT WILL TAKE TOO MUCH TIME
str = str & ("insert into tablename values(" & r.Item("srno") & "," & r.Item("name"));")
Next
EXECUTEcmd(str)
Public Function getdata(ByVal Query As String) As DataTable
connectionString()
Try
Dim mds As New DataTable
Dim mycommand As New SQLiteCommand(DBConn)
mycommand.CommandText = Query
Dim reader As SQLiteDataReader = mycommand.ExecuteReader()
mds.Load(reader)
Return mds
Catch ex As Exception
MsgBox("DB Error", vbCritical, "")
MsgBox(Err.Description)
Return Nothing
End Try
End Function
Public Sub EXECUTEcmd(ByVal selectcmd As String)
Using cn = New SQLiteConnection(conectionString)
cn.Open()
Using transaction = cn.BeginTransaction()
Using cmd = cn.CreateCommand()
cmd.CommandText = selectcmd
cmd.ExecuteNonQuery()
End Using
transaction.Commit()
End Using
cn.Close()
End Using
End Sub
here the Conncection String is:
conStr = "Data Source=" & dbpath & ";Version=3;Compress=True; UTF8Encoding=True; PRAGMA journal_mode=WAL; cache=shared;"
Use a stringbuilder to build your string, not string concatenation
Dim strB As StringBuilder = New StringBuilder(100 * 50000)
For Each r As DataRow In xtable.Rows
strB.AppendLine($"insert into tablename values({r.Item("srno")},{r.Item("name")});")
Next
Strings cannot be changed in .net. Every time you make a new string VB has to copy everything out of the old string into a new one and add the new bit you want. If each of your insert statements is 100 bytes, that means it copies 100 bytes, then adds 100, then copies 200 bytes and adds 100, then copies 300 bytes, then 400 bytes, then 500 bytes. By the time it has done 10 strings it has made 5.5 kilobytes of copying. By the time it's done 50 thousand strings it has copied 125 gigabytes of data. No wonder it's slow!
Always use a StringBuilder to build massive strings
--
I'm willing to overlook the sql injection hacking nag for this one, because of the nature of the task, but please read http://bobby-tables.com - you should never, ever concatenate values into an SQL as a way of making an sql that has some varying effect.
This entire exercise would be better done as this (pseudocode) kind of thing:
Dim sel as New SQLiteCommand("SELECT a, b FROM table", conn)
Dim ins as New SQLiteCommand("INSERT INTO table VALUES(:a, :b)", conn)
ins.Parameters.Add("a" ...)
ins.Parameters.Add("b" ...)
Dim r = sel.ExecuteReader()
While r.Read()
ins.Parameters("a") = r.GetString(0)
ins.Parameters("b") = r.GetString(1)
ins.ExecuteNonQuery()
End While
That is to say, you minimize your memory by reading rows one at a time out of ther edaer and inserting them one at a time in the insert; the insert command is prepared once, you just change the parameter values, execute it, change them again, execute it ... It's what parameterized queries were designed for (as well as stopping your app getting hacked when someone puts SQL in your variable, or even just stopping it crashing when you have an person named O'Grady
Maybe you must refactor your code like this:
Dim xtable As DataTable = getdata("select * from tablename")
Using cn = New SQLiteConnection(conectionString)
cn.Open()
Using transaction = cn.BeginTransaction()
Try
Using cmd = cn.CreateCommand()
cmd.Transaction = transaction
For Each r As DataRow In xtable.Rows ''''HERE IT WILL TAKE TOO MUCH TIME
cmd.CommandText = "insert into tablename values(" & r.Item("srno") & "," & r.Item("name") & ")"
cmd.ExecuteNonQuery()
Next
End Using
transaction.Commit()
Catch ex As Exception
transaction.Rollback()
End Try
End Using
End Using
Public Function getdata(ByVal Query As String) As DataTable
connectionString()
Try
Dim mds As New DataTable
Dim mycommand As New SQLiteCommand(DBConn)
mycommand.CommandText = Query
Dim reader As SQLiteDataReader = mycommand.ExecuteReader()
mds.Load(reader)
Return mds
Catch ex As Exception
MsgBox("DB Error", vbCritical, "")
MsgBox(Err.Description)
Return Nothing
End Try
End Function
Instead of concatenate an possible giant string, wrap all your inserts into a single transaction, like above. This will reduce the memory used and also make sqlite perform faster.

How do I read HasRow then Update?

Is it possible to read HasRow and then Update ?
This is the code what I have tried so far :
If conn.State = ConnectionState.Closed Then
conn.Open()
End If
Dim sqlcmd As New MySqlCommand("SELECT * FROM tbl_online_attendance where employees_id = '" & lvRealAtt.Items(itms).SubItems(0).Text & "' and in_time = '" & lvRealAtt.Items(itms).SubItems(1).Text & "' ", conn)
Dim dr As MySqlDataReader
dr = sqlcmd.ExecuteReader
If dr.HasRows Then
Dim query As String
query = "UPDATE tbl_online_attendance SET out_time = '" & lvRealAtt.Items(itms).SubItems(2).Text & "' where employees_id = '" & lvRealAtt.Items(itms).SubItems(0).Text & "' and in_time = '" & lvRealAtt.Items(itms).SubItems(1).Text & "' "
sqlcmd.Connection = conn
sqlcmd.CommandText = query
sqlcmd.ExecuteNonQuery() 'It error in this part
Else
End If
But it give's me error saying:
There is already an open DataReader associated with this Connection which must be closed first
Please avoid commenting Use Parameters Your code is Prone to SQL injection attack
You should not have to check connection state if you keep your connections local to the method that they are used. Database objects like connections and commands need to be closed and disposed as soon as possible. Using...End Using blocks take care of this for you even if there is an error. Don't open a connection until directly before the .Execute....
Don't pull down data when you only need Count. .ExecuteScalar returns the first column of the first row of the result set, which in this case, is the Count. If you have a large table you need to look into If Exists which will stop as soon it finds a match whereas Count looks at the whole table.
Always use Parameters. Never concatenate strings to build sql queries to avoid sql injection. I had to guess at the datatypes of the parameters. Check your database to get the actual types and adjust the code accordingly.
Private Sub OPCode(ByVal itms As Integer)
Dim RowCount As Integer
Using conn As New MySqlConnection("Your connection string"),
sqlcmd As New MySqlCommand("SELECT Count(*) FROM tbl_online_attendance where employees_id = #id and in_time = #inTime;", conn)
sqlcmd.Parameters.Add("#id", MySqlDbType.Int32).Value = CInt(lvRealAtt.Items(itms).SubItems(0).Text)
sqlcmd.Parameters.Add("#inTime", MySqlDbType.String).Value = lvRealAtt.Items(itms).SubItems(1).Text
conn.Open()
RowCount = CInt(sqlcmd.ExecuteScalar)
If RowCount > 0 Then
sqlcmd.CommandText = "UPDATE tbl_online_attendance SET out_time = #outTime where employees_id = #id and in_time = #inTime;"
sqlcmd.Parameters.Add("#outTime", MySqlDbType.String).Value = lvRealAtt.Items(itms).SubItems(2).Text
sqlcmd.ExecuteNonQuery()
End If
End Using
End Sub
Every SqlDataReader needs to be closed before you could execute another query.
so i suggest you to separate the sqlreader and the update query, put the reader into a boolean function to check either a row with those parameters exist or not.
Private Function HasRow(ByVal employeeid As Integer, ByVal date as DateTime) As Boolean
HasRow = False
Dim reader As SqlDataReader
'do select query here
'use the if reader.Read if you want to
HasRow = reader.HasRows
reader.Close()
End Function
Call the function before updating, if it's True then proceed the update. Let's say we put it into a Update Sub.
Private Sub Update()
If HasRows(parameters here) Then
'update query here
End If
End Sub

VB Access Update data type mismatch

Sorry in advance, Im new to vb,
Im creating a small application for assignment as a MCQ quiz, Im using the same form but use the function of [nextques] proceed to next question and use [answer] to update the user answer to database(access).
I previously uses SQL Server and it work good but due to i need to run the application in school's computer where they wont allow me to install anything, I have no idea at all then decided to use access as it is easier to export and include it in debug file.
Ive go through some topics as well as did some changes however the problem persist. I set the [input] in access as integer, i did put parameter on it but it still not working.
ERROR code : Data type mismatch in criteria expression
Public Function answer(ans As Integer)
Try
con.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=mom.mdb;"
con.Open()
cmd.Connection = con
' Update the answer 1234 (as abcd) according to id in lblnum.text
Dim updatecmd As String = "UPDATE question SET [input] = #ans WHERE id = '" & lblnum.Text & "'"
cmd.Parameters.AddWithValue("#ans", ans)
cmd.CommandText = updatecmd
updatecmd = cmd.ExecuteNonQuery()
con.Close()
'Proceed to next question by calling Function NextQues
nextques()
Catch ex As Exception
MsgBox(ex.Message)
End Try
Return 0
End Function
I tried also to make it like this however another error shows: COM object that has been separated from its underlying RCW cannot be used
Public Function answer(ans As Integer)
Dim countid As Integer = 1
Try
con.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=mom.mdb;"
con.Open()
cmd.Connection = con
Dim updatecmd As String = "UPDATE question SET [input] = #ans WHERE id = #countid"
cmd.Parameters.AddWithValue("#countid", countid)
cmd.Parameters.AddWithValue("#ans", ans)
cmd.CommandText = updatecmd
updatecmd = cmd.ExecuteNonQuery()
countid = countid + 1
con.Close()
'Proceed to next question by calling Function NextQues
nextques()
Catch ex As Exception
MsgBox(ex.Message)
End Try
Return 0
End Function
change this line
Dim updatecmd As String = "UPDATE question SET [input] = #ans WHERE id = '" & lblnum.Text & "'"
to
Dim updatecmd As String = "UPDATE question SET [input] = #ans WHERE id = " & lblnum.Text

the user must match to the 3 fields

Dim elem As String
elem = "Grade School"
Dim v As Integer
v = 0
Dim con As New SqlConnection("SERVER=ANINGDZTS-PC;DATABASE=AEVS;Trusted_Connection = yes;")
Dim cmd As SqlCommand = New SqlCommand("SELECT * FROM tbl_Voter WHERE Department, VotersID = '" & elem & "''" & txt_PwordElem.Text & "'AND Voted ='" & v & "'", con)
con.Open()
Dim sdr As SqlDataReader = cmd.ExecuteReader()
Try
If (sdr.Read() = False) Then
high()
Else
MessageBox.Show("WELCOME!")
elemBallot.Show()
Me.Hide()
End If
Catch EX As Exception
MsgBox(EX.Message)
End Try
End Sub
this code is not working, an error appear," An expression of non-boolean type specified in a context where a condition is expected, near ','."
Instead of trying to create your SQL query via concatenating strings, which is prone to errors, a much better way is to use parametrized query. Change your SqlCommand declaration to
Dim cmd As SqlCommand = New SqlCommand("SELECT * FROM tbl_Voter WHERE Department = #Department AND VotersID = #VotersID AND Voted = #Voted", con)
cmd.Parameters.AddWithValue("#Department", elem)
cmd.Parameters.AddWithValue("#VotersID", txt_PwordElem.Text)
cmd.Parameters.AddWithValue("#Voted", v)
Bonus: Avoid SQL Injection
P.S. Please don't forget to close your Reader and Connection after the use.
P.P.S. If you simple need to make sure that specific row in table "tbl_Voter" exists or not based on your parameters (which is, judging by the code what you're doing) - using DataReader is overkill. Consider query like SELECT 1 FROM tbl_Voter ... and use of ExecuteScalar to check returned value for Nothing

Problem injecting a VB parameter into a stored procedure (FireBird)

Everyone here has always been such great help, either directly or indirectly. And it is with grand hope that this, yet again, rings true.
For clarification sakes, the Stored Procedure is running under FireBird and the VB is of the .NET variety
I have a stored procedure (excerpt below, important bit is the WHERE)
select pn, pnm.description, si_number, entry_date, cmp_auto_key,
parts_flat_price, labor_flat_price, misc_flat_price, woo_auto_key,
wwt_auto_key
from parts_master pnm, wo_operation woo
where pn like :i_pn || '%'
and pnm.pnm_auto_key = woo.pnm_auto_key
into :pn, :description, :work_order, :entry_date, :cmp, :parts_price,
:labor_price, :misc_price, :woo, :wwt
I am trying to pass a parameter from a vb app, that uses the parameter I_PN, the code of which follows below (The variables for MyServer and MyPassword are determined form an earlier part of the code.)
Try
Dim FBConn As New FirebirdSql.Data.FirebirdClient.FbConnection()
Dim FBCmd As FirebirdSql.Data.FirebirdClient.FbCommand
Dim MyConnectionString As String
MyConnectionString = _
"datasource=" & MyServer & ";database=" & TextBox4.Text & "; & _
user id=SYSDBA;password=" & MyPassword & ";initial catalog=;"
FBConn = New FirebirdSql.Data.FirebirdClient. & _
FbConnection(MyConnectionString)
FBConn.Open()
FBConn.CreateCommand.CommandType = CommandType.StoredProcedure
FBCmd = New FirebirdSql.Data.FirebirdClient. & _
FbCommand("WIP_COSTS", FBConn)
FBCmd.CommandText = "WIP_COSTS"
FBConn.CreateCommand.Parameters. & _
Add("#I_PN", FirebirdSql.Data.FirebirdClient.FbDbType.Text). & _
Value = TextBox1.Text
Dim I_PN As Object = New Object()
Me.WIP_COSTSTableAdapter.Fill(Me.WOCostDataSet.WIP_COSTS, #I_PN)
FBConn.Close()
Catch ex As System.Exception
System.Windows.Forms.MessageBox.Show(ex.Message)
End Try
When I execute the VB.App and try to run the program, I get the following Error:
Dynamic SQL Error
SQL Error Code = -206
Column Unknown
I_PN
At Line 1, column 29
And I can't quite put my finger on what the actual problem is. Meaning, I don't know if my logic is incorrect on the VB side, or, on the Stored Procedure.
Any coding that is included is kludged together from examples I have found with various bits of code found during long sojourns of GoogleFu.
As anyone with more than a month or two of experience (unlike me) with VB can attest with merely a glance - my code is probably pretty crappy and not well formed - certainly not elegant and most assuredly in operational. I am certainly entertaining all flavors of advice with open arms.
As usual, if you have further questions, I will answer them to the best of my ability.
Thanks again.
Jasoomian
After a little rethinking and a bit more research, I finally got my code working..
Try
' Code for checking server location and required credentials
Dim FBConn As FbConnection
' Dim FBAdapter As FbDataAdapter
Dim MyConnectionString As String
MyConnectionString = "datasource=" _
& MyServer & ";database=" _
& TextBox4.Text & ";user id=SYSDBA;password=" _
& MyPassword & ";initial catalog=;Charset=NONE"
FBConn = New FbConnection(MyConnectionString)
Dim FBCmd As New FbCommand("WIP_COSTS", FBConn)
FBCmd.CommandType = CommandType.StoredProcedure
FBCmd.Parameters.Add("#I_PN", FbDbType.VarChar, 40)
FBCmd.Parameters("#I_PN").Value = TextBox1.Text.ToUpper
Dim FBadapter As New FbDataAdapter(FBCmd)
Dim dsResult As New DataSet
FBadapter.Fill(dsResult)
Me.WIP_COSTSDataGridView.DataSource = dsResult.Tables(0)
Dim RecordCount As Integer
RecordCount = Me.WIP_COSTSDataGridView.RowCount
Label4.Text = RecordCount
Catch ex As System.Exception
System.Windows.Forms.MessageBox.Show _
("There was an error in generating the DataStream, " & _
"please check the system credentials and try again. " &_
"If the problem persists please contact your friendly " &_
"local IT department.")
End Try
' // end of line
I had also thought that I would need to make changes to the actual stored procedure, but, this turned out to be incorrect.
The code may not be pretty, and I need to do more work in my TRY block for better error handling; but, it works.
Thanks to all who chimed in and helped me get on track.
Try changing this:
FBConn.CreateCommand.Parameters. & _
Add("#I_PN", FirebirdSql.Data.FirebirdClient.FbDbType.Text). & _
Value = TextBox1.Text
... to this:
FBCmd.Parameters.AddWithValue("#I_PN", TextBox1.Text)
Basically, you want to add stored procedure parameters to the Command object, not the Connection object.
Andreik,
Here is the entire stored Procedure. And our Firebird is Version 1.5.3, written with IbExpert version 2006.12.13, Dialect 3
Begin
For
select pn, pnm.description, si_number, entry_date, cmp_auto_key, parts_flat_price,
labor_flat_price, misc_flat_price, woo_auto_key, wwt_auto_key
from parts_master pnm, wo_operation woo
where pn like :i_pn || '%'
and pnm.pnm_auto_key = woo.pnm_auto_key
into :pn, :description, :work_order, :entry_date, :cmp, :parts_price,
:labor_price, :misc_price, :woo, :wwt
Do begin
labor_hours = null;
work_type = null;
parts_cost = null;
labor_cost = null;
ro_cost = null;
customer = null;
select company_name
from companies
where cmp_auto_key = :cmp
into :customer;
select work_type
from wo_work_type
where wwt_auto_key = :wwt
into :work_type;
select sum(sti.qty*stm.unit_cost)
from stock_ti sti, stock stm, wo_bom wob
where sti.wob_auto_key = wob.wob_auto_key
and sti.stm_auto_key = stm.stm_auto_key
and wob.woo_auto_key = :woo
and sti.ti_type = 'I'
and wob.activity <> 'Work Order'
and wob.activity <> 'Repair'
into :parts_cost;
select sum(sti.qty*stm.unit_cost)
from stock_ti sti, stock stm, wo_bom wob
where sti.wob_auto_key = wob.wob_auto_key
and sti.stm_auto_key = stm.stm_auto_key
and wob.woo_auto_key = :woo
and sti.ti_type = 'I'
and wob.activity = 'Repair'
into :ro_cost;
select sum(wtl.hours*(wtl.fixed_overhead+wtl.variable_overhead+wtl.burden_rate)),
sum(wtl.hours)
from wo_task_labor wtl, wo_task wot
where wtl.wot_auto_key = wot.wot_auto_key
and wot.woo_auto_key = :woo
into :labor_cost, :labor_hours;
suspend;
end
End
Hardcode - I responded in the comments to your suggestion.