Retrieving Stored Procedure Results - sql-server

I'm trying to switch a user between three different screens depending on what a stored procedure returns in a BtnView_Click procedure in asp.net VB. The SP would return a "0, 1, or NULL". Currently it's only returning a "1" and not the others. I'm having trouble with the Reader.Read area with the IF statement and i'm wondering if there's a simple fix to this so it directs everything accuratley.
This is what I currently have (updated)
Sub BtnView_Click(ByVal sender As Object, ByVal e As CommandEventArgs)
Session.Add("SvarRecord", e.CommandArgument)
Dim sb As New System.Text.StringBuilder()
Dim connectionString As String = ConfigurationManager.ConnectionStrings("CS_Connection").ConnectionString
Using connection As New SqlConnection(connectionString)
Dim myCommand As New SqlCommand("View", connection)
myCommand.CommandType = CommandType.StoredProcedure
Dim sqlRecord As SqlParameter = New SqlParameter("#Name", SqlDbType.VarChar)
sqlRecord.Value = Session("SvarRecord")
myCommand.Parameters.Add(sqlRecord)
connection.Open()
Using reader As SqlClient.SqlDataReader = myCommand.ExecuteReader
REM Read() returns True if data can be read
If reader.Read() Then
REM IsDbNull checks if given column (by ordinal) contains DbNull.
REM You need it because you can not convert DbNull to a number. As alternative
REM you may read it as object and compare by yourself.
If reader.IsDBNull(0) Then
Response.Redirect("Entry.Aspx")
REM We are sure it is not DbNull and we can assume it is an integer
ElseIf reader.GetInt32(0) = 0 Then
Response.Redirect("Negatives.Aspx")
ElseIf reader.GetInt32(0) = 1 Then
Response.Redirect("PrevEntry.Aspx")
End If
End If
reader.Close()
End Using
connection.Close()
connection.Dispose()
End Using

You're comparing HasRows property (a Boolean that indicates if recordset is empty or not), not value returned from your stored procedure.
Change your code to:
Using reader As SqlClient.SqlDataReader = myCommand.ExecuteReader
Rem Read() returns True if data can be read
If reader.Read() Then
Rem IsDbNull checks if given column (by ordinal) contains DbNull.
Rem You need it because you can not convert DbNull to a number.
Rem As alternative you may read it as object and compare by yourself.
If reader.IsDbNull(0) Then
Response.Redirect("Entry.Aspx")
Rem We are sure it is not DbNull and we can assume it is an integer
ElseIf reader.GetInt32(0) = 0 Then
Response.Redirect("Negatives.Aspx")
ElseIf reader.GetInt32(0) = 1 Then
Response.Redirect("PrevEntry.Aspx")
End If
End If
End Using
Here I assume your stored procedure returns an integer value. If it's not you can get/compare with right value or convert it to integer. First case (same for the other If):
ElseIf reader.GetString(0) = "0" Then
Second case:
ElseIf Convert.ToInt32(reader.GetObject(0)) = 0 Then
Last note about your code, as suggested by Jhon in his comment your code may fail for an unlimited number of reasons, you'd better to always wrap disposable objects in a Using statement like this:
Dim connectionString As String =
ConfigurationManager.ConnectionStrings("CS_Connection").ConnectionString)
Using connection As New SqlConnection(connectionString)
Rem Put here all code that uses connection
End Using
This will ensure connection and other shared (and limited!) resources will always be released even in case of error. Edit: compiler won't complain about comparison of a Boolean and a String because you didn't set OPTION STRICT to ON (see #Tim's answer for more details).

First, use the Using-statement to dispose/close the connection and anything else implementing IDisposable even on error. Second, you should really set OPTION STRICT to on globally, then this will not compile which is a good thing:
If reader.HasRows = "0" Then
The problem with that code is that HasRows is a Boolean but you are comparing it with a String. That would result in a compiler error normaly, but OPTION STRICT off allows it. The Boolean will be converted to a String implicitely. So this comparison seems to work but it does not.
Actually you have to read the field, you can use the Get... methods:
If reader.HasRows Then
If reader.IsDBNull(0) Then
Response.Redirect("Entry.Aspx")
ElseIf reader.GetInt32(0) = 1 Then
Response.Redirect("PrevEntry.Aspx")
ElseIf reader.GetInt32(0) = 0 Then
Response.Redirect("Negatives.Aspx")
End If
End If

Related

Slow For Each Element in Array Loop with Instr

I need to check if values (approx 10.000 insecure passwords) stored in a one-dimensional array occur in a TextBox and display a warning if necessary.
But due to the constant checking after each keystroke the input into the TextBox is very delayed.
Does anyone know a faster way to do this?
Greetings Ronny
'ForbiddenPasswords = one-dimensional array
Private Sub txtAdminPassword_Change()
Dim VarDat As Variant
For Each VarDat In ForbiddenPasswords
If InStr(1, LCase(txtAdminPassword.Text), VarDat) > 0 Then
lblInsecureWarning.Visible = True
Exit For
End If
Next
End Sub
Rather than loading the list of banned passwords in an array and searching through it to find if it exists, consider using a table to store the (lower case) passwords. You can then open a recordset based on the text entered, and see if it returns any records. Something like:
Function fCheckBannedPWD(strPWD As String) As Boolean
' returns true if the entered password is banned, otherwise false
On Error GoTo E_Handle
Dim db As DAO.Database
Dim rsPWD As DAO.Recordset
Dim strSQL As String
Set db = CurrentDb
strSQL = "SELECT BannedPWD FROM tblPWD WHERE InStr('" & LCase(strPWD) & "',BannedPWD)>0;"
Set rsPWD = db.OpenRecordset(strSQL)
If Not (rsPWD.BOF And rsPWD.EOF) Then
fCheckBannedPWD = True
End If
fExit:
On Error Resume Next
rsPWD.Close
Set rsPWD = Nothing
Set db = Nothing
Exit Function
E_Handle:
MsgBox Err.Description & vbCrLf & vbCrLf & "fCheckBannedPWD", vbOKOnly + vbCritical, "Error: " & Err.Number
Resume fExit
End Function
Regards,
Arrays want to be iterated with For...Next; Object collections want to be iterated with For Each...Next. Using For Each to iterate a large array is going to be slower than it needs to be.
Converting 10K strings to lowercase is also taking a toll on performance.
Using InStr is verifying whether the password contains the forbidden password, not whether it is one of them: assuming you mean to check if the user's password exists in the forbidden passwords list, you don't need InStr.
If you want to stick with an array, use For...Next to iterate it, and specify Option Compare Text to have the = operator treat "string" the same as "STRING" (default is Option Compare Database in Access, which matches your database settings - in all other hosts it's Option Compare Binary, which is case-sensitive).
But you shouldn't need to iterate any array. Assuming the 10K strings are all unique, you can have them added to a keyed Collection, and then you can instantly know if the user's password is in the collection by trying to add the user's password to the collection:
Private Function IsForbiddenPassword(ByVal Value As String) As Boolean
'assuming a ForbiddenPasswords global collection exists
On Error Resume Next
ForbiddenPasswords.Add Value, Value 'if the key already exists, an error is raised
If Err.Number <> 0 Then
'key already exists: password is forbidden
IsForbiddenPassword = True
Else
'.Add was successful, password is not forbidden (gotta remove it now!)
ForbiddenPasswords.Remove Value
End If
On Error GoTo 0
End Function
Alternatively, use a Scripting.Dictionary and its Exists method, which returns True if a given key already exists:
Private Sub txtAdminPassword_Change()
'where ForbiddenPasswords is a Dictionary instance keyed with the passwords:
lblInsecureWarning.Visible = ForbiddenPasswords.Exists(Value)
End Sub
Try the next way, please:
Create a Private variable on top of the module (in the declarations area):
Private objExcel As Object
Copy the next function code:
Function passExists(strPass As String, arr) As Boolean
Dim lPath As String
lPath = UCase(strPass)
passExists = Not IsError(objExcel.Application.match(lPath, arr, 0))
End Function
It can be checked in the next way:
Sub checkPasswordInArray()
Dim ForbiddenPasswords, boolMatch As Boolean, strPass As String
If objExcel Is Nothing Then Set objExcel = CreateObject("Excel.Application")
ForbiddenPasswords = Split("pass1,pass2,pass3", ",")
strPass = "Pass2" 'txtAdminPassword.Text
Debug.Print passExists(strPass, ForbiddenPasswords)
End Sub
In order to be very fast, objExcel must not be created each time you need to use the function, but you must take care to quit after using it...
Just to be complete, 2 additions to the propositions found.
txtAdminPassword.Text LIKE vardat & "*" is about 6x faster than
InStr(1, LCase(txtAdminPassword.Text), VarDat) > 0
for even better perf with your array, you can use Filter:
Dim strSubNames As Variant
strSubNames = Filter(forbiddenPasswords, txtAdminPassword.Text)

How to fix 'Index was outside of bounds of the array'

Using visual basic. Trying to load a series of reports onto a listview, listview consists of 3 columns (location, date and severity level) everytime it loads it crashes due to 'index being outside the bounds of the array'.Specifically around DOI = reportdetails(1) in my code. It is loading off of a textfile. I have the data within the textfile so I am unsure of why it is saying I am asking for information that doesnt exist. The program also encypts the textfile.
Dim locate, DOI, SeverityLevel, ReportTitles, EReportTitles, ReportDetails(2) As String
Dim Index As Integer 'Define Variables
Dim FileNum As Integer = FreeFile()
Dim IncidentReport As ListViewItem
lstReports.Items.Clear()
If Dir("ReportTitles.txt") <> "" Then 'If the directory of the file exits then continue
FileOpen(FileNum, "ReportTitles.txt", OpenMode.Input) 'open file
Do Until EOF(FileNum) 'Repeat until the end of the file is reached
EReportTitles = "" 'Clear variables, to safeguard against crashes or errors
ReportTitles = ""
EReportTitles = LineInput(FileNum) 'EReportTitles is equal to the current file line
Dim FileName As String = "ReportTitles.txt" 'Define variables
Dim I, C As Integer
Dim Last As Integer = EReportTitles.Length - 1
Dim ThisChar As Char
For I = 0 To Last 'Begin for loop
ThisChar = EReportTitles.Chars(I) 'Decryption of file
C = Asc(ThisChar) Xor 22
ThisChar = Chr(C)
ReportTitles += ThisChar
Next
If ReportTitles <> "" Then
ReportDetails = Split(ReportTitles, ",") 'Split the lines when a "," is encountered
locate = ReportDetails(0) 'Assosciate to relevant value in array
DOI = ReportDetails(1)
SeverityLevel = ReportDetails(2)
IncidentReport = New ListViewItem
IncidentReport.Text = locate 'Add relevant values to IncidentReport ListViewItem variable
IncidentReport.SubItems.Add(DOI)
IncidentReport.SubItems.Add(SeverityLevel)
lstReports.Items.Add(IncidentReport) 'Transfer IncidentReport to listview
Else
End If
Loop
FileClose(FileNum) 'close file
End If
Expected result is to load all of the report location, dates and severity levels onto the listview.
Also sorry about the formatting of this question, i'm new to stack overflow.
There's no point declaring ReportDetails like this:
ReportDetails(2) As String
because that creates an array that you never use. Here:
ReportDetails = Split(ReportTitles, ",")
you are creating a new array anyway and the length of that array will be determined by the number of delimiters in ReportTitles. If you're being told that 1 is an invalid index for that array then that array must only contain 1 element, which means that ReportTitles didn't contain any delimiters.
This is not something that we should have to explain to you because you can easily see it for yourself by debugging and you should ALWAYS debug BEFORE posting here. Set a breakpoint at the top of the code, step through it line by line and examine the state at each step. You can easily see the contents of ReportTitles and ReportDetails and anything else to see whether they are what you expect them to be.
If the point here is to read a CSV file then you really ought to be using the TextFieldParser class. The documentation for that class includes a code example.
This requires .Net Standard 2.1, and so I'm not sure if VB.Net can use the required SpanAction for the String.Create() method, but if it is supported it should greatly outperform the original.
lstReports.Items.Clear()
'Read and "Decrypt" (and I use that term loosely) the file with only a single heap allocation
Dim file As String
Using fs As FileStream = File.OpenRead("ReportTitles.txt")
file = String.Create(fs.Length, fs,
Sub(chars, stream)
For i As Integer = 0 To stream.Length - 1
'THIS IS NOT ENCRYPTION! At best, it's obfuscation.
chars(i) = Chr(fs.ReadByte() Xor 22)
Next
End Sub)
End Using
'Use an actual CSV parser
Using reader As New StringReader(file), _
parser As New TextFieldParser(reader)
parser.TextFieldType = FileIO.FieldType.Delimited
parser.Delimiters = New String() {","}
Dim row As String()
While Not parser.EndOfData
row = parser.ReadFields()
If row.Length >= 3 Then
Dim IncidentReport As New ListViewItem()
IncidentReport.Text = row(0) '
IncidentReport.SubItems.Add(row(1))
IncidentReport.SubItems.Add(row(2))
lstReports.Items.Add(IncidentReport)
End If
End While
End Using
If you are not able to use that version, this is not quite as good, but still a better approach than the original:
lstReports.Items.Clear()
'Load and "Decrypt" the file
Dim file As String
Using fs As FileStream = File.OpenRead("ReportTitles.txt")
Dim builder As New StringBuilder(fs.Length)
For i As Integer = 0 To fs.Length - 1
'THIS IS NOT ENCRYPTION! At best, it's obfuscation.
builder.Append(Chr(fs.ReadByte() Xor 22))
Next
file = builder.ToString()
End Using
'Use an actual CSV parser
Using reader As New StringReader(file), _
parser As New TextFieldParser(reader)
parser.TextFieldType = FileIO.FieldType.Delimited
parser.Delimiters = New String() {","}
Dim row As String()
While Not parser.EndOfData
row = parser.ReadFields()
If row.Length >= 3 Then
Dim IncidentReport As New ListViewItem()
IncidentReport.Text = row(0) '
IncidentReport.SubItems.Add(row(1))
IncidentReport.SubItems.Add(row(2))
lstReports.Items.Add(IncidentReport)
End If
End While
End Using
In both cases, use Try/Catch rather than Dir() to check whether the location exists. Just try to open the file. Dir() costs an extra disk seek, and there are precious few things in programming slower than disk I/O.

reduce string to compare

I would like to know the most effective way to compare long strings, having a database sql server, such as:
long string -> compare to-> 5,000 records in database
I happened to convert the records and the string to compare, using crc32, but I'm not sure if it would be a more efficient method
If u have a database,then u can go with a DataTable/DataSet/DataReader,let's assume u are using a DataReader
Dim cmd as new SqlCommand("Select * from table",connectionstring)
Dim dr as SqlDataReader = cmd.ExecuteReader
While dr.Read
If dr(1).ToString = "MyString" Then
''''Something will happen
End if
End While
If there are no other partially identifying fields you could check then make a new field of, perhaps the first 10 characters of the string. Make an index on this field and then take the first 10 characters of string to insert as a parameter. This should reduce the number of long comparisons you need to do. Of course my selection of 10 characters is arbitrary. You would select a number that you can see from your data that would reduce the number of rows returned significantly but be as small as possible.
Private Function OKayToInsert(stringToInsert As String) As Boolean
Using connection As New SqlConnection("Your connection string")
Dim stringShort As String = stringToInsert.Substring(0, 10)
Using cmd = New SqlCommand("Select LongString From StringTable Where NewShortField = #ShortString", connection)
cmd.Parameters.Add("#ShorString", SqlDbType.NVarChar, 100).Value = stringShort
Using reader As SqlDataReader = cmd.ExecuteReader()
If reader.HasRows Then
While reader.Read
If stringToInsert = reader.GetString(0) Then
Return False
End If
End While
Return True
Else
Return True
End If
End Using
End Using
End Using
End Function

How to add database query results to an array

I am trying to select string values from an Access database and then place them into an array of strings so that I can perform a loop statement on the array.
However I don't know how to place the result of the query into an array. I know how to query the database but all I need is how to put the result in an array.
My select statement is Select motonum from moto. I want to put motonum in an array.
The whole code to read the data is:
connect2()
If Not cnn2.State = ConnectionState.Open Then
'open connection
cnn2.Open()
'MessageBox.Show("chk2")
End If
cmd5.Connection = cnn2
cmd5.CommandText = "Select motonum from moto"
myData5 = cmd5.ExecuteReader
While myData5.Read
'code to return results here
End While`
There are any number of different ways to approach this, depending on the actual needs of your project. First and foremost, I would ask if you actually require a string array as the return type. For most cases, an array is less useful that a List(Of String) or other types which implement IEnumerable.
Here are two options, both of which involve a List(Of String). However, one returns the List to the caller, which can then choose to employ the many useful methods of the List type in working with the data:
THIS is the way I would recommend:
Public Function getListOfMotonum() As List(Of String)
Dim SQL As String = "SELECT motonum FROM moto"
Dim output As New List(Of String)()
' Set the connection string in the Solutions Explorer/Properties/Settings object (double-click)
Using cn = New SqlConnection(Properties.Settings.[Default].MyConnectionString)
Using cmd = New SqlCommand(SQL, cn)
cn.Open()
Try
Dim dr = cmd.ExecuteReader()
While dr.Read()
output.Add(dr("motonum").ToString())
End While
Catch e As SqlException
' Do some logging or something.
MessageBox.Show("There was an error accessing your data. DETAIL: " & e.ToString())
End Try
End Using
End Using
Return output
End Function
Here is a trivial example of code which consumes the output of this function:
Private Sub PrintListToConsole()
Dim MyMotonumList = Me.getListOfMotonum()
For Each item As String In MyMotonumList
Console.WriteLine(item)
Next
End Sub
If your project REQUIRES a string array, the approach may vary. You can return a string from the same function with a couple minor modifications:
' Change the return type in the function signature:
Public Function getArrayOfMotonum() As String()
Dim SQL As String = "SELECT motonum FROM moto"
Dim output As New List(Of String)()
' . . . Same Data Access code as above:
' Just use the .ToArray method of the List class HERE:
Return output.ToArray()
End Function
Or, you can use the same method in your client code, consuming the original function which returns a list:
Private Sub PrintArrayToConsole()
Dim MyMotonumArray = Me.getArrayOfMotonum()
For Each item As String In MyMotonumArray
Console.WriteLine(item)
Next
End Sub
Returning the List from your function provides a more flexible return type, with many useful methods.
As a side note, allow me to recommend the Using block when consuming data access resources. This handles the proper tear down and disposal of the Connection and Command objects for you.

SqlDataReader - Multiple connections

I've written a console app in VB.NET to do some database work and a strange runtime error has arisen...
Here's the main code:
Sub Main(ByVal args() As String)
Try
user = args(0)
batchID = args(1)
GetBatchRevision()
'batchRev = 1
Dim getTestScripts As SqlCommand = New SqlCommand("GetTestScriptsInTestBatch", cs)
getTestScripts.CommandType = CommandType.StoredProcedure
Dim batchIDParam As SqlParameter = getTestScripts.Parameters.Add("#batchID", SqlDbType.Int, 4)
Dim batchRevParam As SqlParameter = getTestScripts.Parameters.Add("#batchRev", SqlDbType.Int, 4)
'batchIDParam.Value = 1
'batchRevParam.Value = 1
batchIDParam.Value = batchID
batchRevParam.Value = batchRev
Console.WriteLine(batchID & " " & batchRev)
Console.WriteLine(cs.State)
Console.ReadLine()
Using cs
cs.Open()
Dim reader As SqlDataReader = getTestScripts.ExecuteReader(CommandBehavior.CloseConnection)
While reader.Read()
Console.WriteLine("Executing Test Script " & reader("ScriptID").ToString() & " Revision " & reader("ScriptRev").ToString)
End While
Console.ReadLine()
End Using
Catch ex As Exception
End Try
End Sub
GetBatchRevision:
Private Sub GetBatchRevision()
Using cs
Dim GetNewestRev As New SqlCommand("SELECT Max(BatchRev) FROM TestBatch WHERE BatchID=" & batchID, cs)
cs.Open()
Dim reader As SqlDataReader = GetNewestRev.ExecuteReader(CommandBehavior.CloseConnection)
reader.Read()
If Not IsDBNull(reader(0)) Then
batchRev = reader(0).ToString()
End If
End Using
End Sub
batchRev and batchID are both global variables within the module.
Behaviorally:
The app prints out "1" (user input), "1" (database result), "0" (enum of Closed connection)
When I press Enter to get past the first Console.ReadLine(), the app simply closes out.
If I comment out GetBatchRevision and directly set batchRev = 1, I get the above result as well as "Executing Test Script 1 Revision 52", "Executing Test Script 2 Revision 66" which are the expected results from the stored procedure GetTestScriptsInTestBatch.
The global variable declarations are as follows:
Private batchID As String
Private batchRev As String
Any ideas why GetBatchRevision() causes the app to crash? By itself (removing the stored proc part of the code), it executes just fine. My initial guess was that there was a hanging connection, but ending a "using" block is supposed to close a SQL connection as well as any open readers associated with said connection (as mentioned before, cs.State returns 0).
Your problem is on these lines:
reader.Read()
If Not IsDBNull(reader(0)) Then
reader.Read() is probably returning false; yet you try to access reader(0). Boom!
You should change it to:
IF reader.Read() AndAlso Not IsDBNull(reader(0)) Then
'' etc
End If
It looks like cs is also a global variable. This is a bad idea. .Net data access works a lot better when you're using a new connection each time. You're probably fine in this app, but you're setting up some bad habits. Instead, load your connection string as a global variable and use that when creating your connections.
Next up, there's no reason for GetBatchRevision() to talk to global variables. Have it accept an argument and return it's result instead. And of course I can't overlook the sql injection issue because you concatentate the batchid to the end of your string. Here's the new version of the function after fixing those errors:
Private Function GetBatchRevision(ByVal BatchID As String) As String
Using cn As New SqlConnection(cs), _
GetNewestRev As New SqlCommand("SELECT Max(BatchRev) FROM TestBatch WHERE BatchID= #BatchID", cn)
GetNewestRev.Parameters.Add("#Batch", SqlDbType.Int).Value = Convert.ToInt32(BatchId)
cn.Open()
Return GetNewestRev.ExecuteScalar().ToString()
End Using
End Function
This can get even better if you keep BatchRev and BatchID as int's rather than strings internally.

Resources