Looping through SqlDataReader returns same row twice - vb.net

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 + "'"

Related

Filter and sort datagridview

I have created an item list in which all items are loaded, and I need to filter the data according to the text entered in the textbox.
Please guide me how to filter and sort data, I have created the fallowing code
Private Sub loadsearchitems(item As String)
dgvsearchitem.ScrollBars = ScrollBars.Vertical
On Error Resume Next
con = New System.Data.OleDb.OleDbConnection(connectionString)
con.Open()
Dim ds As DataSet = New DataSet
Dim adapter As New OleDb.OleDbDataAdapter
sqlstr = "SELECT Imaster.icode, Imaster.iname & ' ' & MfgComTab.comname as[Iname], Imaster.unitcode, Imaster.tax,Unit.unitname FROM ((Imaster INNER JOIN MfgComTab ON Imaster.mccode = MfgComTab.mccode) INNER JOIN Unit ON Imaster.unitcode = Unit.unitcode) WHERE (Imaster.isdeleted = 'N') AND Imaster.comcode=#comcode AND Imaster.iname like '%' & #item & '%' order by iname asc "
adapter.SelectCommand = New OleDb.OleDbCommand(sqlstr, con)
adapter.SelectCommand.Parameters.AddWithValue("#comcode", compcode)
adapter.SelectCommand.Parameters.AddWithValue("#iname", item)
adapter.Fill(ds)
If ds.Tables(0).Rows.Count > 0 Then
dgvsearchitem.DataSource = ds.Tables(0)
dgvsearchitem.Columns("unitname").Visible = False
dgvsearchitem.Columns("unitcode").Visible = False
dgvsearchitem.Columns("icode").Visible = False
dgvsearchitem.Columns("tax").Visible = False
dgvsearchitem.Columns("Iname").Width = 371
Else
ds.Dispose()
End If
con.Close()
End Sub
But in the above code the application is slow because every time any text is entered a query is executed. Please tell me any solution where query is executed only once and when we enter the text it only search the items by wild card and filter it.
If you initially set the DataGridView with all the records, then you could avoid to go again to the database to extract your data in a filtered way. You have already extracted everything, so you could simply set the DataSource with a DataView filtered locally
' Code that loads initially the grid
sqlstr = "SELECT Imaster.icode, Imaster.iname .....FROM ...." ' NO WHERE HERE
....
dgvsearchitem.DataSource = ds.Tables(0).DefaultView
Now in your loadsearchitems instead of executing again a query against the database you could take the datasource and set the RowFilter property
Dim v as DataView = CType(dgvsearchitem.DataSource, DataView)
v.RowFilter = "Imaster.comcode='" & compcode & "' AND Imaster.iname like '%" & item & "'%'"
Note how the RowFilter property doesn't understand the use of parameters, so if it is possible for your comcode field to contain single quotes you need to add a some form of doubling the quotes (a String.Replace will do) to avoid a syntax error. And yes, there is no worry for Sql Injection on a DataView (it is a disconnected object and whatever your user types in the compcode field it cannot reach the database)

Create report from Bound DataTable

I'm fairly new to programming.
I don't plan on using Crystal Reports unless it is absolutely necessary because of license fees. I have also looked into .rdlc a little bit and to be honest it confused me. I was not sure how to get the data I wanted into the Client Report Definition using the Report Wizard. As a side note, though, I am dealing with encrypted data.
I am decrypting data in my DataTable, and would like to make a report out of the DataTable that feeds a DGV and show it in a ReportViewer. If there is a better way please let me know!
I am not sure how to use the DataTable as the data source for the report. Here ismy code for both:
Dim dt As DataTable = ds.Tables(1)
ds.DataSetName = "DataSetReport"
dt.TableName = "DataTable1"
If SearchFirsttxt.Text = "" Then
SqlCommand.CommandText = "Select * FROM PARTICIPANT WHERE LAST_NM_TXT = '" & eLast & "';"
ElseIf SearchLastTxt.Text = "" Then
SqlCommand.CommandText = "Select * FROM PARTICIPANT WHERE FIRST_NM_TXT = '" & eFirst & "';"
Else
SqlCommand.CommandText = "Select * FROM PARTICIPANT WHERE FIRST_NM_TXT = '" & eFirst & "' and LAST_NM_TXT = '" & eLast & "';"
End If
'SQL Command returns rows where values in database and textboxes are equal
SearchFirsttxt.Text = ""
SearchLastTxt.Text = ""
dFirst = clsEncrypt.DecryptData(eFirst) 'Decrypts the value entered into the SearchFirsttxt
dLast = clsEncrypt.DecryptData(eLast) 'Decrypts the value entered into the SearchLasttxt
Dim myAdapter As New SqlDataAdapter(SqlCommand) 'holds the data
myAdapter.Fill(dt) 'datatable that is populated into the holder (DataAdapter)
DataGridView1.DataSource = dt 'Assigns source of information to the gridview (DataTable)
Try
For i As Integer = 0 To dt.Rows.Count - 1
dt.Rows(i)("FIRST_NM_TXT") = clsEncrypt.DecryptData(dt.Rows(i)("FIRST_NM_TXT"))
dt.Rows(i)("LAST_NM_TXT") = clsEncrypt.DecryptData(dt.Rows(i)("LAST_NM_TXT"))
Next
Catch ex As Exception
MessageBox.Show("Either the first name or last name did not match. Please check your spelling.")
End Try
I've tried:
Dim ds As DSReportTest
ds.Tables.Add(dt)
which didn't work. The reason I am trying to rely on dt is because it contains the decrypted data.
Printing DataGridView using PrintDocument could be tedious. Refer this codeproject article to get an idea.
You can also use the DataGridView clipboard content to get the formatted values into clipboard and copy into some excel file. The below MSDN link has some example for getting clipboard content from DataGridView.
http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.getclipboardcontent%28v=vs.110%29.aspx
If you are using vs 2010, you should not have a license issue using crystal reports when deploying an application. I assume it's not server based redistribution.
http://www.sap.com/solution/sme/software/analytics/crystal-visual-studio/implement/licensing.html

ExecuteReader error when selecting multiple columns "No data exists for row/column"

I am trying to run a simple query that returns three columns but am running into an error stating that the data does not exist though I am quite certain it does.
This is the section of code in question.
cmd.CommandText = "SELECT school.id, school.city, school.state
FROM school,city
WHERE school.name = '" & SchoolLb.SelectedItem & "'
AND city.name = '" & CityLb.SelectedItem & "';"
'MessageBox.Show(cmd.ExecuteScalar)
myReader = cmd.ExecuteReader
profileSchool = myReader(0)
profileCity = myReader(1)
profileState = myReader(2)
The School list box is populated with schools located in the city that is currently selected in the city list box, so they match up. When I remove the comment tag from MessageBox.Show(cmd.ExecuteScalar) the ExecuteScalar runs and returns a message showing the ID of the correct record. However when using cmd.ExecuteReader I get the error mentioned above.
Thanks for looking.
You need to execute the reader and then advance it to the next record by using reader.Read():
Using reader = cmd.ExecuteReader()
reader.Read()
profileSchool = reader(0)
profileCity = reader(1)
profileState = reader(2)
End Using

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...

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.