Do While Loop doesn't stop looping in VB.net - database

I'm trying to generate a random number that's not in the database. If the randomly generated number happens to already be in the database, a message box appears saying the number exists. When you click Ok, it generates another number and if it's still in the database, it will repeat the same process. With my code, it keeps showing the message box and generating a number even after it has already generated a number that's not in the database. This is my code:
Private Sub BtnOrder_Click(sender As Object, e As EventArgs) Handles BtnOrder.Click
Dim rand As New Random
Dim num As Integer
num = rand.Next(1, 30)
TxtOrder.Text = "#" + num.ToString("0000")
BtnOrder.Enabled = False
Dim connection As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Daily Sales.accdb;")
Dim command As New OleDbCommand("SELECT [Order No] FROM [Table1] WHERE [Order No] = orderno", connection)
Dim orderParam As New OleDbParameter("orderno", Me.TxtOrder.Text)
command.Parameters.Add(orderParam)
command.Connection.Open()
Dim reader As OleDbDataReader = command.ExecuteReader()
Do While reader.HasRows = True
If reader.HasRows = False Then
Exit Do
End If
MessageBox.Show("Order number exists.", "Invalid Order Number")
num = rand.Next(1, 30)
TxtOrder.Text = "#" + num.ToString("0000")
Loop
command.Connection.Close()
End Sub

I think many things need to be changed. Some are listed in comments on your question, but here they are
You hit the database multiple times.
You don't follow the Disposable pattern which is implemented by both the connection and the command.
You had a loop whose condition looked for rows, then immediately checks for no rows to exit, which can never happen.
You don't actually check that the order number is in the result set.
You only create random numbers from 1 to 29, but if they all exist, the loop will continue forever.
You perform the database interaction on the UI thread.
You may find that using Using blocks to properly dispose of the Disposable objects helps with memory, and moving the query off the UI helps your UI remain responsive. Also, I don't see a need to alert the user that a random number has found a match in the database - just find a proper random number without alerting the user. Lastly, throw an Exception if you can't generate another order number.
Dim lowerInclusive As Integer = 1, upperExclusive As Integer = 30
Private Async Sub BtnOrder_Click(sender As Object, e As EventArgs) Handles BtnOrder.Click
BtnOrder.Enabled = False
Try
Dim nextOrderNumber = Await GetNextOrderNumber()
TxtOrder.Text = $"#{nextOrderNumber:0000}"
Catch ex As Exception
TxtOrder.Text = "N/A"
MessageBox.Show(ex.Message)
Finally
BtnOrder.Enabled = True
End Try
End Sub
Private Function GetNextOrderNumber() As Task(Of Integer)
Return Task.Run(
Function()
Dim existingOrderNumbers As New List(Of Integer)()
Using connection As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Daily Sales.accdb;")
connection.Open()
Using command As New OleDbCommand("SELECT [Order No] FROM [Table1]", connection)
Dim reader As OleDbDataReader = command.ExecuteReader()
While reader.Read()
existingOrderNumbers.Add(reader.GetInt32(0))
End While
End Using
End Using
Dim nextOrderNumbers = Enumerable.Range(lowerInclusive, upperExclusive - lowerInclusive).Except(existingOrderNumbers).ToList()
If Not nextOrderNumbers.Any() Then Throw New Exception("All possible order numbers are already used.")
Dim rand As New Random()
Return nextOrderNumbers(rand.Next(0, nextOrderNumbers.Count()))
End Function)
End Function
Thinking about it again, the new order number never goes into the database. So when does that happen? You may want that to be an atomic operation, if this code can be used by multiple people - multiple threads. You can use a transaction to lock down the table... or perhaps just use a PK ID as part of a hash to generate some random number.

So if you're looking for unique numbers, I suggest you change the DB column to become an IDENTITY column. That way, SQL server takes care of creating unique (increasing) numbers for you.
Query the current highest order number:
SELECT MAX([Order No]) AS OrderNoCurrent FROM Table1
Query the next order number:
SELECT (MAX([Order No])+1) AS OrderNoNext FROM Table1

Related

VB Checking amount in SQL table and updating table in SQL

I need some help with my validation. What I'm supposed to do is check weather the number in the textbox is greater than or less than the quantity (stored in an SQL table called inventory) for that specific item (selected from the drop downlist). If the number in the textbox is greater than then quantity for that item then a message pops up saying that there isn't enough quantity for that item. If the number is less than the quantity in the SQL table for that item then it updates that quantity for that item. What my problem is, is that my validation isn't going to my second validation. It's like its stuck at the first if statement and I don't know how to fix it. I feel like its something very simple but I just can't put my finger on it. Here's my code:
Dim DA As New SqlDataAdapter("Select itemID, quantity From Inventory", con)
Dim DT As New DataTable
Try
If DT.Rows.Count > 0 Then DT.Rows.Clear()
DA.Fill(DT)
With DropDownList1
.DataSource = DT
.DataTextField = "itemID"
.DataValueField = "quantity"
.DataBind()
.Items.Insert(0, "Please Select a Customer")
End With
Catch ex As Exception
Response.Write(ex.Message)
End Try
End Sub
Protected Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim PurchaseQuantity, UP, SL As String
PurchaseQuantity = TextBox1.Text
Dim cmdUpdate As New SqlCommand("Update Inventory SET quantity = #p1 where price = #p2", con)
With cmdUpdate.Parameters
.Clear()
.AddWithValue("#p1", PurchaseQuantity)
.AddWithValue("#p2", DropDownList1.SelectedValue)
End With
Dim cmdSelect As New SqlCommand("Select quantity From Inventory where itemID = #p1", con)
With cmdSelect.Parameters
.Clear()
.AddWithValue("#p1", DropDownList1.SelectedValue)
End With
Try
If con.State = ConnectionState.Closed Then con.Open()
UP = cmdUpdate.ExecuteNonQuery
SL = cmdSelect.ExecuteScalar
Catch ex As Exception
Response.Write(ex.Message)
Finally
con.Close()
End Try
If PurchaseQuantity > SL Then
MsgBox("There is not enough quantity for this item", vbExclamation, "Error")
Else
If PurchaseQuantity < SL Then
MsgBox("Inventory is updated", vbExclamation, "Error")
UP -= PurchaseQuantity
End If
End If
So, you're seeing the error "There is not enough quantity for this item"?
I see a few problems:
Update Inventory SET quantity = #p1 where price = #p2
should be:
Update Inventory SET quantity = quantity - #p1 where itemID = #p2
Also, the order of the commands is backwards. The SELECT should happen before the UPDATE. (Otherwise, you're deducting the quantity from your inventory, THEN checking that you have enough to fill the order.)
Also, this whole operation should be transaction-bounded. You're open to a race condition, of sorts, with the current code. (If (2) simultaneous orders are placed, you could end up with a negative inventory quantity.) Or, at least, put a check constraint on the table (Inventory.quantity > 0) so it forces a SQL Exception in VB and halts the update.

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

Has random have a limit?

Im working on a program which generating random numbers. I have use random in generating the build number. I just wondering does random will have a limit? heres my code thank you in advance.
Private Sub agenerate_Click(sender As Object, e As EventArgs) Handles agenerate.Click
Dim rand As New Random
abuildnumber.Text = rand.Next
Dim exist As String = String.Empty
exist &= "select * from stocks "
exist &= "where build_number=#build"
Using conn As New SqlConnection("server=WIN10;user=admin;password=12345;database=pc_parts")
Using cmd As New SqlCommand
With cmd
.Connection = conn
.CommandType = CommandType.Text
.CommandText = exist
.Parameters.AddWithValue("#build", abuildnumber.Text)
End With
Try
conn.Open()
Dim reader As SqlDataReader = cmd.ExecuteReader
If reader.HasRows Then
reader.Close()
abuildnumber.Text = rand.Next
End If
abrand.Enabled = True
apart.Enabled = True
aquantity.Enabled = True
aday.Enabled = True
amonth.Enabled = True
ayear.Enabled = True
add.Enabled = True
conn.Close()
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Using
End Using
End Sub
The Random will never "run out" of numbers, if that's what you are asking, but it will generate duplicates. You should probably avoid creating a new Random instance every time though, as that can lead the the numbers being less randomly distributed across all possible values. You should create a single source of random numbers, and draw from that each time, without recreating it. A class-level field would work well for this in your example.
If you are interested on just "random looking" numbers, with an extremely low chance of duplicates, you may want to investigate Guid.
Your code does contain another issue that concern me, mainly that the line abuildnumber.Text = rand.Next shows that you have Option Strict set to Off. This is a very bad practice that you should try your hardest to avoid. It can introduce subtle bugs that can be hard to track down once your code becomes more complicated.
Good job with the Using statement! I'm impressed that you are using it correctly on both the SqlConnection and the SqlCommand, most people miss the latter.
Yes, Random Numbers have limit. because its return INT value so its limits as per Int data type
Parameters
minValue
Type: System.Int32
The inclusive lower bound of the random number returned.
maxValue
Type: System.Int32
The exclusive upper bound of the random number returned. maxValue must be greater than or equal to minValue.
Return Value Type:
System.Int32 A 32-bit signed integer greater than
or equal to minValue and less than maxValue; that is, the range of
return values includes minValue but not maxValue. If minValue equals
maxValue, minValue is returned.
Here is Reference https://msdn.microsoft.com/en-us/library/2dx6wyd4(v=vs.100).aspx

Getting Multi Rows in Database and transferring it in a multiline textbox in VB.net WinForms

Here in my code, i have a database which has table of my applicants. As you will see in the code below, i want to get the number of rows from my command text and transfer it to the string "abc"
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
myr.Close()
mycom.Connection = cn
mycom.CommandText = "SELECT Count(Cellphone) FROM tbl_applicant where Gender='Female';"
myr = mycom.ExecuteReader
Dim abc As String
If myr.Read Then
abc = myr(0)
End If
myr.Close()
On the code Below i used the abc as the number of data i must acquire. Then i used the new query to get the values i wanted to and transfer them to a String Array, as you can see I Redim the universal variable Numb to abc to have its array boundery.
mycom.CommandText = "SELECT Cellphone FROM tbl_applicant where Gender='Female';"
myr = mycom.ExecuteReader
ReDim Numb(abc)
If myr.Read Then
For i As Integer = 1 To abc.ToString - 1
LOT = myr(0).ToString
LOT = LOT + (myr(i).ToString + ",") <- this is where i get the error it says that index is our of range.
Numb = LOT.Split(",")
Next
End If
In this code below, i want the values of Variable Numb() to be transferred to a multiline textbox
Dim sbText As New System.Text.StringBuilder(500)
For i As Integer = 0 To Numb.Length - 2
' This will convert the number to a string, add it to the stringbuilder
' and then append a newline to the text buffer
sbText.AppendLine(Numb(i))
Next i
' Now move the buffer into the control
TextBox1.Text = sbText.ToString()
End Sub
The end value i must see in the textbox should be like
11111111111
11111111112
11111111113
11111111114
and so forth, please try to understand the numbers i am referring it to real phone numbers. Any help with the problem or solution maybe.. Thanks
I don't think you need to first query the db to get the count of records before then going back to the db to get the phonenumbers, you could just do this:
mycom.CommandText = "SELECT Cellphone FROM tbl_applicant where Gender='Female';"
myr = mycom.ExecuteReader
While myr.Read()
TextBox1.Text = TextBox1.Text & myr(0) & Environment.NewLine
End While
No need for array's or List's
While this is just a rough guide and an attempt at understanding your issue, try the code and see if it works 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