Problem injecting a VB parameter into a stored procedure (FireBird) - vb.net

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.

Related

Looping through SqlDataReader returns same row twice

I'm making a VB.NET (4.6) Windows form application that collects info on our servers and allows us to do reports on it. It's coming together nicely but I've run into an issue I can't figure out. One part of the project is a service that queries the info on available Windows updates from WSUS and then stores them in an SQL database - that part works fine. I'm now trying to present this data in a DataGridView using an SqlDataReader to query the info from the database and fill up a DataTable with the response. The problem is that when I use the reader, it puts the same record in the DataTable twice. I'm not sure what I'm doing wrong, and I'm sure it's something super simple. Perhaps one of you folks can spot the problem?
Note: Earlier in the application, the updateid's are stored as unique strings in a list called dbupdateidlist, the results are stored in a DataTable called dbupTable, and the datagridview I'm trying to update is called UpdateDeetsView.
Public Sub getUpdateDetails()
For Each str As String In dbupdateidlist
Dim commGetUpdateDetails As String = "select upTableId, title, classification, description, " +
"releasedate, severity, articlenumber, url from updatedetails where updateid = '" + str + "'"
Using connObj As New SqlClient.SqlConnection(connectionString)
Using cmdObj As New SqlClient.SqlCommand(commGetUpdateDetails, connObj)
connObj.Open()
Using readerObj As SqlClient.SqlDataReader = cmdObj.ExecuteReader
While readerObj.Read
dbuptabid = readerObj("uptableid")
dbuptitle = readerObj("title")
dbupclass = readerObj("classification")
dbupdesc = readerObj("description")
dbupreleasedate = readerObj("releasedate")
dbupseverity = readerObj("severity")
dbuparticlenumber = readerObj("articlenumber")
dbupurl = readerObj("url")
row = dbupTable.NewRow()
row("uptableid") = dbuptabid
row("title") = dbuptitle
row("classification") = dbupclass
row("description") = dbupdesc
row("releasedate") = dbupreleasedate
row("severity") = dbupseverity
row("articlenumber") = dbuparticlenumber
row("url") = dbupurl
dbupTable.Rows.Add(row)
End While
End Using
connObj.Close()
End Using
End Using
Next
UpdateDeetsView.DataSource = dbupTable
End Sub
Forgive the likely terrible code, I'm an SA not a dev...
Try this:
Public Sub getUpdateDetails()
Dim sql As String = _
"SELECT DISTINCT upTableId, title, classification, description, releasedate, " & _
" severity, articlenumber, url " & _
" FROM updatedetails " & _
" WHERE updateid = #updateID"
Using cn As New SqlClient.SqlConnection(connectionString), _
cmd As New SqlClient.SqlCommand(sql, cn)
cmd.Parameters.Add("#updateID", SqlDbType.Int).Value = Int32.Parse(dbupdateidlist.First())
cn.Open()
UpdateDeetsView.DataSource = cmd.ExecuteReader()
End Using
End Sub
Note the use of DISTINCT and the complete lack of any explicit loops whatsoever.
Also note that I'm only looking at one entry in dbupdateidlist. The real source of your old bug may have been to have the the ID in that list twice.
There is no way readerObj.Read is failing to move to the next record
get the output of this and run it is SSMS
"select upTableId, title, classification, description, " +
"releasedate, severity, articlenumber, url from updatedetails where updateid = '" + str + "'"

Combination of INSERT and SELECT statement causes error in VB

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

Looking for the best way to use ExecuteScalar()

This code works. It's based on some code I found on the internet.
Can you tell me if the coding is the best way to get a scalar value and if there is a better way can you show coding samples?
Dim objParentNameFound As Object
TextBoxParentsName.Text = ""
If TextBoxParentID.Text <> "" Then
' Display the parent's name using the parent ID. '
Dim strSqlStatement As String = "Select FatherName " & _
"From Parents " & _
"Where ID = #SearchValue"
' Set up the sql command and lookup the parent. '
Using objSqlCommand As SqlCommand = New SqlCommand(strSqlStatement, ObjConnection)
With objSqlCommand
' Add SqlParameters to the SqlCommand. '
.Parameters.Clear()
.Parameters.AddWithValue("#SearchValue", TextBoxParentID.Text)
' Open the SqlConnection before executing the query. '
Try
ObjConnection.Open()
Try
objParentNameFound = .ExecuteScalar()
If objParentNameFound <> Nothing Then
' Display the parent name here. '
TextBoxParentsName.Text = objParentNameFound
End If
Catch exSqlErrors As SqlException
MessageBox.Show("Sorry, I couldn't execute your query because of this error: " & _
vbCrLf & vbCrLf & exSqlErrors.Message, _
"Error")
End Try
Catch exErrors As Exception
MessageBox.Show("Sorry, there was an error. Details follow: " & _
vbCrLf & vbCrLf & exErrors.Message, _
"Error")
Finally
ObjConnection.Close()
End Try
End With
End Using
End If
The Microsoft Access Application block has some nice examples of how to use ADO.Net. In particular what you might find helpful is how they've organized tasks such as ExecuteScalar() into a series of overloaded methods making it easy to invoke the process you need.
The sample you posted would greatly benefit from separating out the concerns. In other words, take the code you use to build up the connection, command and parameters and make that a separate method or methods. This allows the code to be reused without copy& pasting it throughout your codebase. This allows your calling code to simply pass in the parameter(s) and bind the result to your text box or other controls.
Edit: Example
Assuming you use the SqlHelper.vb class you can do something like the following:
Dim searchValue As Integer = 1
Dim myConnectionString As String = "MyConnectionString"
Dim sqlStatement As String = "SELECT FatherName FROM Parents WHERE ID = #SearchValue"
Dim paramList(0) As SqlParameter
paramList(0) = New SqlParameter() With {.Value = searchValue, .ParameterName = "#SearchValue"}
TextBoxParentsName.Text = SqlHelper.ExecuteScalar(myConnectionString, CommandType.Text, sqlStatement, paramList).ToString()

Trying to update record, keep getting this error vb.net

I'm sure this question will be easy for you lot... :)
I'm simply trying to update an existing record in my database using the following:
Private Sub Button12_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button12.Click
If Not cnn.State = ConnectionState.Open Then
cnn.Open()
End If
cmd2.Connection = cnn
cmd2.CommandText = "UPDATE HireItemRecord SET HireItemBeginDate = " & TextBox45.Text & _
" ,HireItemEndDate = " & TextBox44.Text & _
" ,HireItemCost = " & TextBox16.Text & _
" ,PaymentMethod = " & TextBox17.Text & _
" ,Staff_Id = " & TextBox19.Text & _
" ,HireItemNotes = " & TextBox18.Text & _
" ,HireItemReturnDate = " & TextBox43.Text & _
"WHERE HireRecord_Id = " & TextBox13.Text
cmd2.ExecuteNonQuery()
ds1.Clear()
daHireItemRecord.Fill(ds1, "PersonDetails")
cnn.Close()
End Sub
However no matter what record is selected and whatever details are in the boxes I keep getting this same error over and over:
SqlException was unhandled
Incorrect syntax near '12'.
When there is absolutely nothing in the textboxes the error changes to:
Incorrect syntax near ','.
I'm very new to this and I just can't seem to understand why this is happening.
Thank you very much for your help. :)
So much wrong with this.
You need a space after each comma, not before it.
You should be escaping your values before using them in the query. If I put "0 WHERE 1=1 -- " in any of your text boxes, it'll trash your entire table.
You should ALWAYS name your form controls properly. If I sent you back to this code in a year's time and told you there was a problem with TextBox44, would you know what it means? Same goes for your variables. Sometimes it's ok to have i, x or tbl for a variable name, but in general they should be descriptive.
Example for #2, where I've put "'1/1/1999' WHERE 1=1 --" into TextBox45:
`UPDATE HireItemRecord SET HireItemBeginDate = '1/1/1999' WHERE 1=1 -- , HireItemEndDate...`
Everything after the -- becomes a comment, so you get this:
`UPDATE HireItemRecord SET HireItemBeginDate = '1/1/1999' WHERE 1=1`
Can you imagine what would happen if I executed that query? Nothing good.
You should use parameterized queries, as per the recommendations in this question: Algorithm to avoid SQL injection on MSSQL Server from C# code?
You should never use string concatenation to build SQL. It leaves you open to SQL Injection attacks. Try using the SQLCommand object provided in .Net. This allows you to "parameterize" your query and you don't have to worry about where to put " and '.
It will also allow you add parameters naturally without having to convert them to strings. Something like this:
Dim command As New SqlCommand("SELECT * FROM Table", connection)
command.Parameters.Add("#ID", SqlDbType.Int)
command.Parameters("#ID").Value = customerID
I stole that code from the documentation about SQL Parameters here.

SqlDataReader getting rows when no data

This is incredibly urgent, I need to present this application in 3 and a half hours.
My application checks against a data source to see if a value exists in the database and changes values depending on whether or not the value in question was found.
The problem is that I've run the sql query with the value in question in SSMS and no rows were returned, and yet, my DataReader says it has rows.
This means that my application is reporting inaccurately.
Here's my code:
Using conn As New SqlConnection("Data Source=.\SQLEXPRESS; Initial Catalog=Testing; Integrated Security=True;")
Dim cmd As New SqlCommand(sql, conn)
conn.Open()
Dim reader As SqlDataReader = cmd.ExecuteReader
If reader.HasRows Then
Value = True
AcctNumber = reader(1)
End If
reader.Close()
End Using
I've removed code that's not relevant to this post, but what you may want to know is:
Value is Boolean
AcctNumber is a String
As this is an application for work, I'd rather not include the SQL Query. The problem is the reader. If I comment out Value = True, I get the right info, but leaving that out will mean that in a case where Value should be True, it'll report inaccurately as well.
Thanks in advance!
EDIT: Full Source Code:
Case "Business"
' Change the number format to local because that's what it is in the db
If Microsoft.VisualBasic.Left(NumberToCheck, 2) = "27" Then
NumberToCheck = NumberToCheck.Replace(Microsoft.VisualBasic.Left(NumberToCheck, 2), "0")
End If
Dim sql As String = "SELECT a.TelNumber, c.AccountNumber " & _
"FROM TelInfo a " & _
"INNER JOIN Customers b ON a.CustID = b.pkguidId " & _
"INNER JOIN Accounts c ON b.pkguidId = c.CustID " & _
"WHERE a.TelNumber = '" & NumberToCheck & "'"
Using conn As New SqlConnection("Data Source=.\SQLEXPRESS; Initial Catalog=Testing; Persist Security Info=True; " & _
"User Id=JoeSoap; Password=paoseoj;")
Dim cmd As New SqlCommand(sql, conn)
conn.Open()
Dim reader As SqlDataReader = cmd.ExecuteReader
If reader.Read Then
Value = True
AcctNumber = reader(1)
End If
reader.Close()
End Using
On the comments below made before 08/02/10 (mm/dd/yy):
Value is just a boolean that gets returned by the function to indicate that the searched telephone number (NumberToCheck) exists in the database.
So...
Private AcctNumber As String
Dim val As Boolean = False
val = CheckNumber("3235553469")
If val Then
' AcctNumber will have been set by CheckNumber
Label1.Text = AcctNumber
End If
val will only be returned True if the NumberToCheck (in this example 3235553469) exists in the database.
Having copied the value of NumberToCheck into SSMS and testing the query there, I can verify that the query does work as expected.
No, I can't populate a DataSet because of the volume of information in the table (+/- 9.5m rows). Even with the 'WHERE' filter, the query is too heavy on resources and eventually ends in an OutOfMemory Exception which is why I went with a DataReader.
I'm going to try the ExecuteScalar option as suggested as an answer by Darryl now, will update with the results.
Try changing your If statement to
If reader.Read() Then
Value = True
AcctNumber = reader(1)
End If
HasRows exhibits strange behavior in certain situations, so it's better to avoid it altogether.
This does not solve the problem, but it may get you through your presentation. Use "ExecuteScalar" which should work when you return a single value.
AcctNumber = ""
AcctNumber = cmd.ExecuteScalar
If AcctNumber = "" Then
Value = False
Else
Value = True
End If
Just to be sure it's not a data problem, parameterize your SQL. Change the statement:
Dim sql As String = "SELECT a.TelNumber, c.AccountNumber " & _
"FROM TelInfo a " & _
"INNER JOIN Customers b ON a.CustID = b.pkguidId " & _
"INNER JOIN Accounts c ON b.pkguidId = c.CustID " & _
"WHERE a.TelNumber = #telNumber"
Then do this:
Dim cmd As New SqlCommand(sql, conn)
cmd.Parameters.AddWithValue("#telNumber", NumberToCheck) ' <==== added line
Edit
I think we're looking in the wrong place. Add this just before if reader.Read():
Value = false
AcctNumber = ""
This will make sure you're not having a variable-scope problem. (You are using Option Strict On, right?)
In the immortal words of Homer Simpson... "DOH!!"
I just had a look on the database that my application searches for the telephone numbers and, I don't know how I could have missed it, but the value I'm searching for is actually there.
The problem is, therefore, not likely anything to do with my code, but rather the data...