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
If _id <> String.Empty Then
cn.Open()
cm = New SqlCommand("update tblBill_Items set description = #description,qty = #qty,unitprice=#unitiprice,discount=#discount WHERE id=#id AND invoiceno=#invoiceno", cn)
With cm.Parameters
.AddWithValue("description", TextBox9.Text)
.AddWithValue("qty", CDbl(TextBox8.Text))
.AddWithValue("unitprice", CDbl(TextBox7.Text))
.AddWithValue("discount", CDbl(TextBox6.Text))
.AddWithValue("id", _id)
.AddWithValue("invoiceno", TextBox4.Text)
End With
cm.ExecuteNonQuery()
cn.Close()
Else
cn.Open()
cm = New SqlCommand("insert into tblBill_Items (invoiceno,description,qty,unitprice,discount) values(#invoiceno,#description,#qty,#unitprice,#discount)", cn)
With cm.Parameters
.AddWithValue("invoiceno", TextBox4.Text)
.AddWithValue("description", TextBox9.Text)
.AddWithValue("qty", CDbl(TextBox8.Text))
.AddWithValue("unitprice", CDbl(TextBox7.Text))
.AddWithValue("discount", CDbl(TextBox6.Text))
End With
cm.ExecuteNonQuery()
cn.Close()
End If
In the OP, the data types for the database columns weren't provided which would have been rather useful. I'll go over some potential issues with your code. I say "potential" because without knowing the data types for the database columns and without having some sample input for the Form, it's not possible to know for sure where some of the issues may lie.
It's possible that an issue is occurring due to AddWithValue. You may consider using Add instead and explicitly specifying the data type-which is used in the code below. If the value in one of the TextBoxes is null or empty this will be an issue. In this case, it's necessary to set the value as DBNull.Value.
Both _id <> String.Empty and .AddWithValue("id", _id) seem to indicate that the id column is a string data type such as varchar or nvarchar. However, in your insert statement, id wasn't specified:
cm = New SqlCommand("insert into tblBill_Items (invoiceno,description,qty,unitprice,discount) values(#invoiceno,#description,#qty,#unitprice,#discount)", cn)
When a value is autoincremented upon insertion, it isn't necessary to specify the column during the insertion so it's possible that the data type is a decimal that autoincrements which makes it a good candidate to be the primary key. However, invoiceno should also be unique, which makes it a good candidate to be the primary key. Personally, I would eliminate the id column and use invoiceno as the primary key. However, in the code below, I've made id the primary key and made invoiceno unique, as this is another viable option.
There doesn't seem to be any checking to see if any of the values from the Textboxes are null or empty, although it's possible that this was done in code that wasn't included in the OP. There doesn't seem to be any checking to ensure that a value is numeric prior to converting it to a numeric value. What happens if TextBox8.Text contains abc?
CDbl(TextBox8.Text)
You may consider using Double.TryParse or Decimal.TryParse instead.
In the update SQL statement, WHERE id=#id AND invoiceno=#invoiceno it doesn't seem necessary to include both id and invoiceno since they are both likely unique values.
The following shows how to create a table in SQL Server using VB.NET. It also shows how to both insert data and update data in the table. The code has been tested using SQL Express.
Database name: Order
Table name: tblBill_items
Note: The connection string needs to be modified for your environment. See Connection Strings for more information. The table name was taken from the OP.
In the code below, the id column (in the database table) is decimal and autoincrements. Additionally, invoiceno is unique.
Create a new Windows Forms App (.NET Framework) project
Add a module (name: Helper.vb)
Helper.vb
Imports System.Data.SqlClient
Module Helper
'ToDo: modify the connection string for your environment
Private connectionStr As String = String.Format("Data Source='.\SQLExpress'; Initial Catalog='Order'; Integrated Security=True")
Public Sub CreateTblBillItems()
Dim sqlText As String = "CREATE TABLE tblBill_Items (id decimal NOT NULL IDENTITY(1,1)
CONSTRAINT PK_tblBill_Items_id PRIMARY KEY,
invoiceno nvarchar(50) NOT NULL
CONSTRAINT UQ_tblBill_Items_invoiceno UNIQUE,
description nvarchar(50),
qty decimal NOT NULL,
unitprice decimal(18,2) NOT NULL,
discount decimal(18,2))"
ExecuteNonQuery(sqlText)
End Sub
Private Sub ExecuteNonQuery(sqlText As String)
Try
Using cn As SqlConnection = New SqlConnection(connectionStr)
'open
cn.Open()
Using cmd As SqlCommand = New SqlCommand(sqlText, cn)
'execute
cmd.ExecuteNonQuery()
End Using
End Using
Catch ex As SqlException
Debug.WriteLine("Error (SqlException): " & ex.Message)
Throw ex
Catch ex As Exception
Debug.WriteLine("Error: " & ex.Message)
Throw ex
End Try
End Sub
Private Sub ExecuteNonQueryTblBillItems(sqlText As String, invoiceNo As String, description As String, qty As Double, unitPrice As Decimal, discount As Decimal, Optional id As Decimal = 0)
Try
Using cn As SqlConnection = New SqlConnection(connectionStr)
'open
cn.Open()
Using cmd As SqlCommand = New SqlCommand(sqlText, cn)
With cmd.Parameters
.Add("#id", SqlDbType.Decimal).Value = id
.Add("#invoiceno", SqlDbType.NVarChar).Value = If(String.IsNullOrEmpty(invoiceNo), DBNull.Value, invoiceNo)
.Add("#description", SqlDbType.NVarChar).Value = If(String.IsNullOrEmpty(description), DBNull.Value, description)
.Add("#qty", SqlDbType.Decimal).Value = qty
.Add("#unitprice", SqlDbType.Decimal).Value = unitPrice
.Add("#discount", SqlDbType.Decimal).Value = discount
End With
'ToDo: remove the following code that is for debugging
'For Each p As SqlParameter In cmd.Parameters
'Debug.WriteLine(p.ParameterName & ": " & p.Value.ToString())
'Next
'execute
cmd.ExecuteNonQuery()
End Using
End Using
Catch ex As SqlException
Debug.WriteLine("Error (SqlException): " & ex.Message)
Throw ex
Catch ex As Exception
Debug.WriteLine("Error: " & ex.Message)
Throw ex
End Try
End Sub
Public Sub InsertTblBillItems(invoiceNo As String, description As String, qty As Double, unitPrice As Decimal, discount As Decimal)
Dim sqlText As String = "INSERT INTO tblBill_Items(invoiceno, description, qty, unitprice, discount) VALUES(#invoiceno, #description, #qty, #unitprice, #discount)"
ExecuteNonQueryTblBillItems(sqlText, invoiceNo, description, qty, unitPrice, discount)
End Sub
Public Sub UpdateTblBillItems(invoiceNo As String, description As String, qty As Double, unitPrice As Decimal, discount As Decimal, id As Decimal)
Dim sqlText As String = "UPDATE tblBill_Items set invoiceno = #invoiceno, description = #description, qty = #qty, unitprice = #unitprice, discount = #discount WHERE invoiceno = #invoiceno"
ExecuteNonQueryTblBillItems(sqlText, invoiceNo, description, qty, unitPrice, discount, id)
End Sub
End Module
On Form1, add the Button, Label, and TextBox controls as shown below:
Double-click each of the buttons to add the "Click" event handler.
Form1.vb
Public Class Form1
Private id As Decimal = 0
Private invoiceNo As String = String.Empty
Private description As String = String.Empty
Private qty As Double = 0
Private unitPrice As Decimal = 0
Private discount As Decimal = 0
Private Sub ButtonCreateTable_Click(sender As Object, e As EventArgs) Handles ButtonCreateTable.Click
Helper.CreateTblBillItems()
Debug.WriteLine("Info: Table 'tblBill_Items' created")
End Sub
Private Function IsInputAvailable() As Boolean
If String.IsNullOrEmpty(TextBoxInvoiceNo.Text) Then
MessageBox.Show("InvoiceNo not specified", "Error - InvoiceNo", MessageBoxButtons.OK, MessageBoxIcon.Error)
Return False
End If
If String.IsNullOrEmpty(TextBoxDescription.Text) Then
MessageBox.Show("Description not specified", "Error - Description", MessageBoxButtons.OK, MessageBoxIcon.Error)
Return False
End If
If String.IsNullOrEmpty(TextBoxQty.Text) Then
MessageBox.Show("Qty not specified", "Error - Qty", MessageBoxButtons.OK, MessageBoxIcon.Error)
Return False
End If
If String.IsNullOrEmpty(TextBoxUnitPrice.Text) Then
MessageBox.Show("UnitPrice not specified", "Error - UnitPrice", MessageBoxButtons.OK, MessageBoxIcon.Error)
Return False
End If
Return True
End Function
Private Sub GetData()
'get/set values
'try to convert
Decimal.TryParse(TextBoxId.Text, id)
invoiceNo = TextBoxInvoiceNo.Text.Trim()
description = TextBoxDescription.Text.Trim()
qty = 0
unitPrice = 0
discount = 0
'convert to Double
Double.TryParse(TextBoxQty.Text, qty)
'convert to Decimal
Decimal.TryParse(TextBoxUnitPrice.Text, unitPrice)
If Not String.IsNullOrEmpty(TextBoxDiscount.Text) Then
'convert to Decimal
Decimal.TryParse(TextBoxDiscount.Text, discount)
End If
End Sub
Private Sub BtnInsert_Click(sender As Object, e As EventArgs) Handles BtnInsert.Click
If IsInputAvailable() Then
GetData()
Helper.InsertTblBillItems(invoiceNo, description, qty, unitPrice, discount)
Debug.WriteLine("Info: Data inserted into 'tblBill_Items'")
End If
End Sub
Private Sub ButtonUpdate_Click(sender As Object, e As EventArgs) Handles ButtonUpdate.Click
If IsInputAvailable() Then
GetData()
Helper.UpdateTblBillItems(invoiceNo, description, qty, unitPrice, discount, id)
Debug.WriteLine("Info: Table 'tblBill_Items' updated")
End If
End Sub
End Class
Resources:
Using Statement
SqlConnection Class
SqlCommand Class
SqlParameter Class
Description of the database normalization basics
If _id field is a string and your query is expecting an actual string value you should wrap it in single quotes
I have a dialog with a combobox listing the years an event was held.
Changing the year, changes the following list boxes
One list box 'called inEvent' shows all golfers the attended said event.
The other list box called 'available' that shows every golfer we have in our database that did not attend that years event
It has two buttons. One removes golfers from 'inEvent' and moves them to 'available'. This button works.
The other button does the opposite. It adds available golfers to the selected event year. But it gives me the error -
"The statement has been terminated. Cannot insert the value NULL into column 'intGolferEventYearID', table 'dbo.TGolferEventYears'; column does not allow nulls. INSERT fails."
Changing any line of code in VB results in a different error. So I think the error has to come from SQL itself which I don't know much about. Only other thing I can think of is the listbox is giving the wrong information.
Private Sub btnAddAuto_Click(sender As Object, e As EventArgs) Handles btnAddAuto.Click
Dim strInsert As String = ""
Dim cmdInsert As OleDb.OleDbCommand ' used for our Select statement
Dim dt As DataTable = New DataTable ' table we will load from our reader
Dim intRowsAffected As Integer
' open the DB
OpenDatabaseConnectionSQLServer()
' Build the select statement
strInsert = "INSERT INTO TGolferEventYears ( intGolferID, intEventYearID) Values (" & lstAvailable.SelectedValue & ", " & cboEvents.SelectedIndex + 1 & ")"
' Retrieve all the records
cmdInsert = New OleDb.OleDbCommand(strInsert, m_conAdministrator)
intRowsAffected = cmdInsert.ExecuteNonQuery()
' close the database connection and reload the form so the changes are shown
CloseDatabaseConnection()
frmEventsGolfers_Load(sender, e)
End Sub
I separated the data access code from the user interface. This will make it easy to remove data access entirely from the Form if you later desire.
Private Sub MoveGolfer(GolfID As Integer, YearID As Integer)
'If you keep your connections local you can be sure they are
'closed and disposed which is what the Using...End Using blocks do.
Using cn As New SqlConnection("Your connection string")
Using cmd As New SqlCommand("INSERT INTO TGolferEventYears ( intGolferID, intEventYearID) Values (#Golf, #Year;")
'Always use parameters to protect against Sql injection
cmd.Parameters.Add("#Golf", SqlDbType.Int).Value = GolfID
cmd.Parameters.Add("#Year", SqlDbType.Int).Value = YearID
cn.Open()
cmd.ExecuteNonQuery()
End Using
End Using
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim GolferID As Integer = CInt(lstAvailable.SelectedValue)
Dim Year As Integer = cboEvents.SelectedIndex + 1
Try
MoveGolfer(GolferID, Year)
Catch ex As Exception
'Catch a more specific exception and handle accordingly
MessageBox.Show(ex.Message)
End Try
End Sub
Since I don't really understand SQL I didn't realize my PK didn't have the IDENTITY tag added in the table. Adding this fixed my issue.
Here is the code I added
Dim strSelect As String
Dim cmdSelect As OleDb.OleDbCommand
Dim drSourceTable As OleDb.OleDbDataReader
Dim intNextHighestRecordID As
strSelect = "SELECT MAX(intDealerAutos) + 1 AS intNextHighestRecordID " &
" FROM TDealerAutos"
'Excute command
cmdSelect = New OleDb.OleDbCommand(strSelect, m_conAdministrator)
drSourceTable = cmdSelect.ExecuteReader
'Read result( highest ID )
drSourceTable.Read()
'Null? (Empty Table)
If drSourceTable.IsDBNull(0) = True Then
'Yes, start numbering at 1
intNextHighestRecordID = 1
Else
'No, get the next highest ID
intNextHighestRecordID = CInt(drSourceTable.Item(0))
End If
'Build the select statement
strInsert = "INSERT INTO TDealerAutos ( intDealerAutos, intDealerID, intAutoID)" &
"Values (" & intNextHighestRecordID & ", " & cboDealers.SelectedValue & ", " & lstAvailable.SelectedValue & ")"
Error: Dynamic SQL generation for the UpdateCommand is not supported against a SelectCommand that does not return any key column information
I am trying to solve the following problem- getting my application to write back to my database. The app is hitting the exception title listed above. I have been reading about database CRUD operations here- http://www.homeandlearn.co.uk/NET/nets12p7.html
I know from other reading I have done that I need to have a primary key. Can someone explain how this relates when you are using a data set and data adapter? Is this error related to my actual database or the in memory dataset?
I also realize that I have created a dataset based on one query. Do I need a different query (INSERT) when I reach a ticket not found condition?
Private Sub BtnQuery_Click(sender As Object, e As EventArgs) Handles BtnQuery.Click
sql = "SELECT [Ticket ID] AS Ticket_ID , [Foundstone] AS Foundstone, [ID] AS ID FROM [Table MAIN] WHERE ([Ticket Days OverDue] >= 0)" 'define the query
da = New OleDb.OleDbDataAdapter(sql, con)
da.Fill(ds, "MAIN")
If ds.Tables("MAIN").Rows.Count > 0 Then
TxtRows.Text = ds.Tables("MAIN").Rows.Count
maxrows = Val(TxtRows.Text.ToString)
End If
For i = 0 To maxrows - 1
If i >= 0 Then
result = ds.Tables("MAIN").Rows(i).Item("Ticket_ID")
WebBrowser1.Navigate("https://fs-enterprise.my.private.url/remediation/ticket.exp?ticket=" & result)
Do While WebBrowser1.ReadyState <> WebBrowserReadyState.Complete
Application.DoEvents()
Loop
WebBrowser1.AllowNavigation = True
'<div id="MessageGood_0" class="mvm-status-message msm-msg msm-msg-img" style="background-image:url(/images/fam/cross.png);">The specified ticket does not exist, or is not currently available.</div>
For Each el As HtmlElement In WebBrowser1.Document.GetElementsByTagName("div")
If (el.GetAttribute("id").Equals("MessageGood_0")) Then
TxtTicket.Text = ds.Tables("MAIN").Rows(i).Item("Ticket_ID")
'Try
Dim cb As New OleDb.OleDbCommandBuilder(da)
cb.QuotePrefix = "[" 'allows update if table name or field is a reserved word in MS Access
cb.QuoteSuffix = "]" 'allows update if table name or field is a reserved word in MS Access
ds.Tables("MAIN").Rows(i).Item("Foundstone") = "Not Found"
da.Update(ds, "MAIN")
da.UpdateCommand = cb.GetUpdateCommand()
MsgBox("Ticket Not Found")
'Catch ex As Exception
'MsgBox(ex.Message.ToString, , "Error")
'End Try
Else
'<input class="boldbutton" type="button" value="Verify" onclick="this.form.knob.value='ReqVerify';this.form.verify.value=1;this.form.submit()"></td>
TxtTicket.Text = ds.Tables("MAIN").Rows(i).Item("Ticket_ID")
For Each element As HtmlElement In WebBrowser1.Document.GetElementsByTagName("INPUT")
If (element.GetAttribute("value").Equals("Verify")) Then
element.InvokeMember("click")
End If
Next
End If
Next
End If
Next
MessageBox.Show("All Tickets Have Been Processed")
Me.Close()
con.Close()
End Sub
The answer to your first question is that the primary key needs to be set on the actual database table rather than the in-memory dataset.
In answer to your second question, you can update the database from your dataset using the following:
Dim cb As New OleDb.OleDbCommandBuilder(da)
ds.Tables("MAIN").Rows(i).Item("Foundstone") = "Not Found"
da.UpdateCommand = cb.GetUpdateCommand()
da.Update(ds, "MAIN")
I am making a vb .net winform project that uses a sql server database. A user inputs the details of a person (firstName, lastName, company, contactNumber, etc.) visiting a factory into textboxes, hits the save details button, and this saves the data in the datatable. This works fine, the problem is the next part. The user is then redirected to another form where the input details are shown from the database. This works for the first record but not for any record input after that, I get an error that says "There is no row at position 'n'" and the following line of code is highlighted in the form_Load:
txtFirstName.Text = CStr(dtVisitorDetails.Rows(CInt(CDbl(txtIdNumber.Text) - 1)).Item(1))
It is telling me that any rows after row 0 are not there but I know they are because I have input them and they are showing up in the datatable in the sql server database manager.
I cannot sort this problem, any help with this would be greatly appreciated. I am attaching the rest of the code that's involved with this problem.
Thanks in advanced.
Private Sub previousVisitor_Load(sender As Object, e As EventArgs) Handles MyBase.Load
connectionString = "Data Source=.\SQLExpress;InitialCatalog=Visitors;" & _
"IntegratedSecurity=True;MultipleActiveResultSets=True"
sqlVisitorDetails = "SELECT * FROM visitorDetails WHERE idNumber=#idNumber"
sqlCon.Open()
sqlCmd = New SqlCommand(sqlVisitorDetails, sqlCon)
sqlCmd.Parameters.AddWithValue("#idNumber", txtIdNumber.Text)
dtVisitorDetails = loadDtVisitorDetails()
txtFirstName.Text = CStr(dtVisitorDetails.Rows(CInt(CDbl(txtIdNumber.Text) - 1)).Item(1))
txtLastName.Text = CStr(dtVisitorDetails.Rows(CInt(CDbl(txtIdNumber.Text) - 1)).Item(2))
txtCompany.Text = CStr(dtVisitorDetails.Rows(CInt(CDbl(txtIdNumber.Text) - 1)).Item(3))
txtContactNumber.Text = CStr(dtVisitorDetails.Rows(CInt(CDbl(txtIdNumber.Text) - 1)).Item(4))
txtCountryCode.Text = CStr(dtVisitorDetails.Rows(CInt(CDbl(txtIdNumber.Text) - 1)).Item(5))
txtEmail.Text = CStr(dtVisitorDetails.Rows(CInt(CDbl(txtIdNumber.Text) - 1)).Item(7))
sqlCmd.Dispose()
sqlCon.Close()
End Sub
Private Function loadDtVisitorDetails() As DataTable
Dim dtVisitorDetails As DataTable = Nothing
sqlVisitorDetails = "SELECT * FROM visitorDetails WHERE idNumber=" & txtIdNumber.Text
dtVisitorDetails = fillDtVisitorDetails(sqlVisitorDetails)
Return dtVisitorDetails
End Function
Public Function fillDtVisitorDetails(ByVal sqlVisitorDetails As String) As DataTable
Dim dtVisitorDetails As New DataTable
Dim da As New SqlDataAdapter
Dim conCmd As New SqlCommand
conCmd.CommandText = sqlVisitorDetails
da.SelectCommand = conCmd
da.SelectCommand.Connection = sqlCon
dtVisitorDetails.Columns.GetEnumerator()
da.Fill(dtVisitorDetails)
Return dtVisitorDetails
End Function
Since you can only have 1 row for each unique ID number, when you search and filter as WHERE idNumber = ID Number in form, you should get at most 1 record. That means the DataTable can have at most 1 row.
When you access the Rows of a DataTable like dt.Rows(ID), you are trying to access the row at position number ID in the collection of rows. Therefore, when you do dtVisitorDetails.Rows(CInt(CDbl(txtIdNumber.Text) - 1) for any value of ID number greater than 1, it will fail because that index simply does not exist in the table.
Instead, you should directly use dtVisitorDetails.Rows(0) to access the row. Since you have filtered by the ID Numberm you will only get the details for that person by doing so. Finally, remember to add a check for the row count in the table, so that if you search for an ID which has no records, there will not be an error in trying to retrieve from the table.
Pseudo-code:
If dtVisitorDetails.Rows.Count > 0
txtFirstName.Text = CStr(dtVisitorDetails.Rows(0).Item(1))
...
End If