SqlHelper.ExecuteReader() throws an exception when no results found - sql-server

I have an SqlDataReader that is declared like this:
Dim myReader As SqlDataReader
myReader = SqlHelper.ExecuteReader(ConnectionString, "storedProcedure1", CInt(myTextBox.Text))
Later I use the results like this:
If myReader.HasRows Then
While myReader.Read()
Row = Table1.NewRow()
Row.Item("REF") = myReader.GetString(0)
Row.Item("CD") = myReader.GetString(1)
Row.Item("NAME") = myReader.GetString(2)
Row.Item("KEY") = myReader.GetDecimal(3)
Row.Item("STRING") = myReader.GetString(0) & " - " & myReader.GetString(1) & " - " & myReader.GetString(2).ToString().Replace("'", "") & " - " & myReader.GetString(4).ToString().Replace("'", "")
Table1.Rows.Add(Row)
'Fill Drop Down
drpMenu.Items.Add(New ListItem(myReader.GetString(0) & " - " & myReader.GetString(1) & " - " & myReader.GetString(2).ToString().Replace("'", "") & " - " & myReader.GetString(4).ToString().Replace("'", "")))
End While
End If
myTextBox is a textbox that the user enters a possible location number that gets searched for using the stored procedure. If the user enters a valid location number, this works great and I have no problems. If they enter a non-existent location number, I get an exception:
System.IndexOutOfRangeException: Index was outside the bounds of the array.
I would think that the If myReader.HasRows line would keep me from trying to read and manipulate results that don't exist but there must be something I'm missing. myTextBox is already being validated elsewhere in the code to make sure the user typed in an integer without any wacky characters so bad input doesn't seem to be the problem either.
How do I find out whether the location number exists before calling SqlHelper.ExecuteReader()? Or maybe the better question is how do I gracefully handle this exception and tell the user the location wasn't found?
EDIT: Here's the stored procedure.
ALTER PROCEDURE [dbo].[storedProcedure]
-- Add the parameters for the stored procedure here
#MBR as integer
AS
BEGIN
EXEC ('{CALL RM#IMLIB.spGETLOC( ?)}', #MBR) at AS400
END
EDIT #2: When I run the stored procedure in SMS and pass a valid location, it returns what I expect. If I pass an invalid location number, it returns nothing.

First. create a local variable...and cast the textbox value to the local variable...to make sure that isn't the error.
dim myValue as Int32
myValue = '' convert textbox value to an int.
Second...typically, my datareader code looks like this.
If (Not ( reader Is Nothing) ) then
If reader.HasRows Then
Do While reader.Read()
Console.WriteLine(reader.GetInt32(0) _
& vbTab & reader.GetString(1))
Loop
End If
End If
(You'll have to adjust the GetInt32 or GetString and the ordinal number to your specific case of course).
My guess is that your child-procedure (RM#IMLIB.spGETLOC) has logic in it that does NOT return a row if there isn't a match.
You can still return a result....that has no rows in it. ** (Read that again).
For example
Select ColA, ColB from dbo.MyTable where 0=1
This will return a result, with no rows. That is different from not returning a(ny) select statement..(Read that again)
My guess is that this little nuance is where you get an issue.
APPEND:
If you cannot change the child-stored procedure....
Create a #TempTable...
Populate the #TempTable with the child-stored procedure.
Do a select from the #TempTable.
Here is a generic example:
IF OBJECT_ID('tempdb..#TempOrders') IS NOT NULL
begin
drop table #TempOrders
end
CREATE TABLE #TempOrders
(
ColumnA int
, [ColumnB] nchar(5)
)
/* Note, your #temp table must have the exact same columns as returned by the child procedure */
INSERT INTO #TempOrders ( ColumnA, ColumnB )
exec dbo.uspChildProcedure ParameterOne
Select ColumnA, ColumnB from #TempOrders
IF OBJECT_ID('tempdb..#TempOrderDetails') IS NOT NULL
begin
drop table #TempOrderDetails
end

I finally figured out my issue.
Later in my code, I had a DataRow array that was empty if the location couldn't be found. I was getting the exception because it was trying to grab an Item from array(0) which makes complete sense.
I missed it initially because I thought it was a DataRow, not a DataRow array. Man, I hate fixing up code I didn't write...

Related

Comboboxes visual basic 6

I have multiple comboboxes where users have the option of selecting an item. If no item is selected I insert NULL into SQL-SERVER:
if cboSchool.text="" then
g_strSQL = g_strSQL & "NULL,"
else
g_strSQL = g_strSQL & "'" & cboschool.itemdata(cboschool.listindex) & "',"
End if
My problem is as follow: Later on I allow the user to edit the information that they previously didn't select. So later on when they want to edit the information, I need to be able to have the application realize that there was a value or there wasn't a value in the table in (SQLSERVER), compare if its different from the value that was selected. And if the information is different then need to update the table. This is what my code looks for EDIT:
If g_RS!SchoolID <>cboSchool.ItemData(cboSchool.ListIndex)Then
g_strSQL2 = g_strSQL2 & " School ID = '" & cboSchool.ItemData (cboSchool.ListIndex) & "',"
End If
The problem Im seeing is that g_RS!SCHOOLID shows as "NULL", however it does notice that the value is different from cboschool.itemdata and it just skips to the end of the if statement. I don't understand how it doesn't see the difference.
If Val("" & g_RS!SchoolID) <> cboSchool.ItemData(cboSchool.ListIndex) Then
It's because of the null value that it doesn't see a difference.
You could have:
If g_RS!SchoolID <> "Hello world!" Then
g_strSQL2 = "DROP TABLE X"
Else
MsgBox ("g_RS!SchoolID = Hello world!")
End If
and as long as SchoolID is NULL you'd get a messagebox.
You could fix this with something like:
If Iif(IsNull(g_RS!SchoolID), "", g_RS!SchoolID) <> cboSchool.ItemData(cboSchool.ListIndex) Then
'do stuff
End If

How to pass a StringList as part of SqlParameter to obtain results from SqlServer

I am trying to execute a sql query by passing a lists of keys as a parameter of keycodelist, but it doesn't work. I got KeyNotFoundException even though the value exists in database.
How should I fix this issue? Do I need to escape the parameter calling getKeyValue?
I called the function like this.
getKeyValue("'username', 'userid'")
private Function getKeyValue(ByVal keyList as String)
Dim output As Dictionary (Of String, String)
Dim systemkeysql As String = "select key_code, key_value from system_key_table where key_code in (#keycodelist) "
Try
Using connection As New SqlConnection(getConnectionString())
Using command As New SqlCommand(systemkeysql, connection)
With (command.Parameters)
.Add(New SqlParameter("#keycodelist", System.Data.SqlDbType.NVarChar)).Value = keylist
End With
connection.Open()
Using reader As SqlDataReader = command.ExecuteReader()
While reader.Read()
output.Add(reader(0), reader(1))
End While
End Using
End Using
connection.Close()
End Using
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.OkOnly)
End Try
Return Output
End Function
One reason, why I do not like the "oh we had this before, it's a duplicate" thing is, that the linked articles can be quite old and the answers accepted and/or with the highest score might not be the best today... This approach is listed on these pages too, but very low scored. In my opinion it is the fastest and most elegant way to deal with this (at least for one like me avoiding dynamic SQL whenever possible...)
DECLARE #tbl TABLE(ID INT, SomeValue VARCHAR(10));
INSERT INTO #tbl VALUES
(1,'value 1')
,(2,'value 2')
,(3,'value 3')
,(4,'value 4')
,(5,'value 5');
DECLARE #ListOfIDs VARCHAR(20)='1,3,5';
WITH ListOfIDs AS
(
SELECT CAST('<root><r>'+REPLACE(#ListOfIDs,',','</r><r>')+'</r></root>' AS XML) AS IDsAsXML
)
,ListOfIDsResolved AS
(
SELECT x.y.value('.','int') AS ID
FROM ListOfIDs
CROSS APPLY ListOfIDs.IDsAsXML.nodes('/root/r') x(y)
)
SELECT * FROM #tbl AS tbl
INNER JOIN ListOfIDsResolved ON tbl.ID=ListOfIDsResolved.ID

Check code in loop doesn't working properly

I made a code capable of downloading values in a web page and it works well. Since the values are taken in real time, that is, data download is performed only when there are actually changes, I need if a certain value is changed.
I then created a code that allows me to get check from all elements of the array with the updated values and the array with the old values.
The code actually detects when a given result has changed, but for some strange reason, the message appears to infinity, as if it were stuck in a foor that takes no arguments, and this is very strange.
For Each abc As Country_Data In lista
For Each xyz As Country_Data In vecchia_lista
If abc.casa = xyz.casa And abc.ospite = xyz.ospite Then
If abc.Result <> xyz.Result Then
MsgBox(abc.casa & " - " & abc.ospite & " -- " & abc.Result)
Exit For
End If
End If
Next
Next
Variable definition
Dim lista As New List(Of Country_Data)
Dim vecchia_lista As New List(Of Country_Data)
Private Structure Country_Data
Dim casa As String
Dim ospite As String
Dim Result As String
End Structure
how to fix this?
Based on your comments, I'm gathering that you only ever want to see one message box, when it finds the first case where a result has changed (even though there could be others after that).
The Exit For you're using only exits from the inner For loop, and thus the outer For loop continues to loop, which is probably what you're observing.
You can do something like this:
Dim keepLooping As Boolean = True
For Each abc As Country_Data In lista
For Each xyz As Country_Data In vecchia_lista
If abc.casa = xyz.casa And abc.ospite = xyz.ospite Then
If abc.Result <> xyz.Result Then
MsgBox(abc.casa & " - " & abc.ospite & " -- " & abc.Result)
keepLooping = False
Exit For
End If
End If
Next
If (Not keepLooping) Then Exit For
Next
That way when you want to stop looping, it will set the boolean flag and that will be observed by the outer loop, causing it to exit as well.

VB.Net : Create a Recordset with manual inputs and GetRows() methods failing

First time writing on a forum but this one really left me no choice and it seems that nobody had the same problem as I have... not a good sign...
I have a project to use the COM Server of a software we use internally and need to use one of their built-in function which requires a recordset as an input and return another recordset with the results (important because I need to stick with the recordset).
Here's breifly what I tried. I create a recordset from scratch and fill it with some hardcoded data just for testing purposes. Once my recordset is filled, I want to look at the data just to be sure everything works well, but I'll have to do the same eventually with my results.
The problem I get is it seems that the GetRows() method return only 1 row every time depending on the last row I moved to. But once it's called, I cannot get the other records. I'm already using the GetRows() method with an actual query and still with an ADODB recordset and it works perfectly. Building a recodset from scratch seems less easy.
I need to put all my data in an object to work with it. But even if I want to use only a recordset, I cannot access to all data in it. Very fustrating... something I'm missing here...
Error I get: either bof or eof is true or the current record has been deleted
Thanks in advance,
Public Function GetFDBData() As Boolean
Dim filtersView As New ADODB.Recordset
Dim rsFields(1) As Object
Dim fieldsAPT(3, 1) As Object
Dim dataView As Object
Dim i As Integer
rsFields(0) = "Field Name"
rsFields(1) = "Filter"
fieldsAPT(0, 0) = "ISIN"
fieldsAPT(0, 1) = "=CA89*"
fieldsAPT(1, 0) = "Currency"
fieldsAPT(1, 1) = "=CAD"
fieldsAPT(2, 0) = "Line"
fieldsAPT(2, 1) = "=Bond"
fieldsAPT(3, 0) = "Redemption Date"
fieldsAPT(3, 1) = "=20230*"
Try
'Build the recordset containing APT fields and filters (in the same variable fieldsAPT)
filtersView.CursorLocation = ADODB.CursorLocationEnum.adUseClient
filtersView.Fields.Append(rsFields(0), ADODB.DataTypeEnum.adVarChar, 30)
filtersView.Fields.Append(rsFields(1), ADODB.DataTypeEnum.adVarChar, 30)
filtersView.Open(, , ADODB.CursorTypeEnum.adOpenStatic, ADODB.LockTypeEnum.adLockOptimistic)
Dim fieldAPT(1)
For i = 0 To UBound(fieldsAPT)
fieldAPT(0) = fieldsAPT(i, 0)
Console.WriteLine(fieldAPT(0)) 'Works fine
fieldAPT(1) = fieldsAPT(i, 1)
Console.WriteLine(fieldAPT(1)) 'Works fine
filtersView.AddNew(rsFields, fieldAPT)
filtersView.Update()
Console.WriteLine(filtersView.RecordCount) 'I can see 1 2 3 4 no problem here
Next i
Dim xx As Integer = filtersView.RecordCount 'xx is 4 as expected
Console.WriteLine("xx: " & xx)
filtersView.MoveFirst() 'Will move to the first record or whatever record
'dataView = filtersView.GetRows() 'I expected this line to work, but same results
For i = 0 To xx - 1
dataView = filtersView.GetRows()
Console.WriteLine(dataView(i, 0).ToString) 'ISIN, normal
Console.WriteLine(filtersView.RecordCount) 'Still equals 4, normal
Console.WriteLine(filtersView.BOF) 'False, normal
Console.WriteLine(filtersView.EOF) 'True, which is NOT normal
Console.WriteLine(filtersView.AbsolutePosition) 'Get -3 not sure why (position 1 related to 4???)
Console.WriteLine(filtersView.MaxRecords) 'Get 0 not sure why
filtersView.MoveNext() '!!!! Here is where it fails !!!! Cannot go more than i=0
Next i
GetFDBData = True
Catch ex As Exception
MsgBox(ex.Message)
GetFDBData = False
Finally
'Clear memory
filtersView.Close()
End Try
End Function
Also, if I do this,
Dim xx As Integer = filtersView.RecordCount 'xx is 4 as expected
Console.WriteLine("xx: " & xx)
filtersView.MoveLast()
Instead of
Dim xx As Integer = filtersView.RecordCount 'xx is 4 as expected
Console.WriteLine("xx: " & xx)
filtersView.MoveFirst()
It will return "Redemption date" in the for loop after. Which makes sense because it is the last record. But puting movefirst even after does'nt solve the issue... still one row only. So the data is there, but I really can't extract one line and one line only...
Try changing your cursor type to adOpenDynamic
EDIT: OK,your line
dataView = filtersView.GetRows()
is causing your cursor to travel to the end of the recordset, try moving it outside your loop and following it with a new MoveFirst like so
filtersView.MoveFirst() 'Will move to the first record or whatever record
'dataView = filtersView.GetRows() 'I expected this line to work, but same results
dataView = filtersView.GetRows()
filtersView.MoveFirst()
Dim sTemp As String = ""
For i = 0 To xx - 1
'Console.WriteLine(dataView(i, 0).ToString) 'ISIN, normal
Console.WriteLine(filtersView.RecordCount) 'Still equals 4, normal
Console.WriteLine(filtersView.BOF) 'False, normal
Console.WriteLine(filtersView.EOF) 'True, which is NOT normal
Console.WriteLine(filtersView.AbsolutePosition) 'Get -3 not sure why (position 1 related to 4???)
Console.WriteLine(filtersView.MaxRecords) 'Get 0 not sure why
sTemp = sTemp & "(" & dataView(0, i).ToString & ", " & dataView(1, i).ToString & ")"
filtersView.MoveNext() '!!!! Here is where it fails !!!! Cannot go more than i=0
Next i
Console.WriteLine(sTemp)
You can verify that by checking the value of filtersView.AbsolutePosition immediately before and after your call to GetRows
Also, you are reversing rows and columns in dataView, move i to the second subscript position. I put it in a temporary string to make it easier to view in the debugger.

Weird SQL Error (Bug)

So this is really weird.
I run a sql command from .net on sqlserver with a 'Select Count(*)' and get a response like "Needs attention CA" (which is in a varchar of one field of one record of the inner joined tables).
Huh? How can Count(*) return a string? 999 out of 1000 times this code executes correctly. Just sometimes on some clients servers it will throw a string of errors for an hour or so only to miraculously stop again.
This is my sqlcommand:
SELECT Count(*)
FROM patientsappointments
INNER JOIN appointmenttypes
ON patientsappointments.appointmenttypeid =
appointmenttypes.appointmenttypeid
WHERE ( ( patientsappointments.date > #WeekStartDate
AND patientsappointments.date < #WeekFinishDate )
AND ( patientsappointments.status = 'Pending' )
AND ( patientsappointments.doctorid = #DoctorID )
AND ( appointmenttypes.appointmentname <> 'Note' ) )
And these are the parameters:
#WeekStartDate = 24/06/2013 12:00:00 AM (DateTime)
#WeekFinishDate = 1/07/2013 12:00:00 AM (DateTime)
#DoctorID = 53630c67-3a5a-406f-901c-dbf6b6d1b20f (UniqueIdentifier)
I do a sqlcmd.executescalar to get the result. Any ideas?
The actual executed code is:
SyncLock lockRefresh
Dim WeekFulfilled, WeekPending As Integer
Using conSLDB As New SqlConnection(modLocalSettings.conSLDBConnectionString)
Dim mySQL As SqlCommand
mySQL = New SqlCommand("SELECT COUNT(*) FROM PatientsAppointments INNER JOIN AppointmentTypes ON PatientsAppointments.AppointmentTypeID = AppointmentTypes.AppointmentTypeID " & _
"WHERE ((PatientsAppointments.Date > #WeekStartDate AND PatientsAppointments.Date < #WeekFinishDate) AND (PatientsAppointments.Status = 'Pending') " & _
"AND (PatientsAppointments.DoctorID = #DoctorID) AND (AppointmentTypes.AppointmentName <> 'Note'))", conSLDB)
Try
mySQL.Parameters.Add("#WeekStartDate", SqlDbType.DateTime).Value = MonthCalendar1.SelectionStart.Date.AddDays(-MonthCalendar1.SelectionStart.Date.DayOfWeek).AddDays(1)
mySQL.Parameters.Add("#WeekFinishDate", SqlDbType.DateTime).Value = MonthCalendar1.SelectionStart.Date.AddDays(-MonthCalendar1.SelectionStart.Date.DayOfWeek).AddDays(8)
mySQL.Parameters.Add("#DoctorID", SqlDbType.UniqueIdentifier).Value = cboDoctors.SelectedValue
conSLDB.Open()
'got errors here like "Conversion from string "R2/3" to type 'Integer' is not valid." Weird.
'failing on deadlock - maybe due to simultaneous updating from udp event. Try adding random delay to refresh
WeekPending = mySQL.ExecuteScalar
Catch ex As Exception
ErrorSender.SendError("frmAppointmentBook - RefreshHeader 1", ex, New String() {String.Format("mySQL.commandtext: {0}", mySQL.CommandText), _
String.Format("mySQL.Parameters: {0}", clsErrorSender.ParamsListToString(mySQL.Parameters))})
End Try
Me.lblPendingWeek.Text = WeekPending
Try
mySQL.CommandText = "SELECT COUNT(*) FROM PatientsAppointments INNER JOIN AppointmentTypes ON PatientsAppointments.AppointmentTypeID = AppointmentTypes.AppointmentTypeID WHERE " & _
"(PatientsAppointments.Date > #WeekStartDate AND PatientsAppointments.Date < #WeekFinishDate) AND (PatientsAppointments.Status = 'Fulfilled') AND " & _
"(PatientsAppointments.DoctorID = #DoctorID) AND (AppointmentTypes.AppointmentName <> 'Note')"
'didn't get the error here... but just in case...
WeekFulfilled = mySQL.ExecuteScalar
Catch ex As Exception
ErrorSender.SendError("frmAppointmentBook - RefreshHeader 2", ex, New String() {String.Format("mySQL.commandtext: {0}", mySQL.CommandText)})
End Try
conSLDB.Close()
End Using
End SyncLock
The exact error message is:
System.InvalidCastException
Conversion from string "Needs Attention DC" to type 'Integer' is not valid.
Your problem has nothing to do with the COUNT(*) portion of your code. The problem is somewhere else in your query. What that particular error is telling you is that at some point you are comparing a character field (it probably usually contains numbers) to an integer field. One of the values of the character field happens to be "Needs Attention DC". If I had to guess it is probably either patientsappointments.appointmenttypeid or appointmenttypes.appointmenttypeid. Double check the datatype of each of those columns to make sure they are in fact INT. If they are both INT then start checking the other explicitly named columns in your query to see if you have any surprises.
You must have an error somewhere in your implementation...
Per the documentation, count always returns an int data type value.
Since this doesn't always happen, it must be a result of one of the paramenter values that is sent in. This is one of the lbuiggest problems with using dynamic SQL. What I would do is create the dymanic SQl and then store it in a database logging table with the date and time and user who executed it. Then when you get the exception, you can find the exact SQL code that was sent. Most likely you need more controls on the input variables to ensure the data placed in them is of the correct data type.
I am going to make another guess. I am guessing that this is a multi threading issue. You probably are sharing the connection between multiple threads. Once in a while the thread will get that man from somewhere else and execute it. Make sure that the connection variable is local, and only one thread can access it at a time.
As Martin points out, the following answer is wrong. I'm keeping this here to show that this is wrong.
From what everyone has already said, there is a type mismatch on your columns. Since your where clause appears to be fine, and your join is fine, it must be elsewhere. I would check to see if patientsappointments or appointmenttypes are views. Maybe the view has a join that's throwing the exception. Check the schema definition of all your joins/where's. Somewhere in there you're storing integers in a character field. It's fine for most rows, but one of them has your string.
If it's not in your views, it may be a trigger somewhere. The point is that somewhere there is a schema mismatch. Once you find your schema mismatch, you can find the row by querying for that string.

Resources