Executing a query in VB.NET causes GUI bug [duplicate] - sql

This question already has an answer here:
When clicking "btnLogin", my forms decrease in size.
(1 answer)
Closed 4 years ago.
Whenever my program needs too query the database, it'll result in GUI bugs like the form fonts changing randomly, the form itself resizing, the data in the datagrid being mixed together, and it affect the overall program, not just the form that is calling the query function.
I use this connection
"Provider=Microsoft.ACE.OLEDB.12.0;"
Public DBDA As OleDbDataAdapter
Public DBDT As DataTable
DBDT = New DataTable
DBDA = New OleDbDataAdapter(DBCmd)
RecordCount = DBDA.Fill(DBDT)
and here is my Execute Query function:
Public Sub ExecQuery(Query As String)
'RESET QUERY STATS
RecordCount = 0
Exception = ""
Try
'OPEN A CONNECTION
DBCon.Open()
'CREATE DB COMMAND
DbCmd = New OleDbCommand(Query, DBCon)
'LOAD PARAMETERS INTO COMMAND
Params.ForEach(Sub(Par) DbCmd.Parameters.Add(Par))
'CLEAR PARAMETERS LIST
Params.Clear()
'EXECUTE COMMAND & FILL DATA
DBDT = New DataTable
DBDA = New OleDbDataAdapter(DbCmd)
RecordCount = DBDA.Fill(DBDT)
Catch ex As Exception
Exception = ex.Message
End Try
'CLOSE CONNECTION
If DBCon.State = ConnectionState.Open Then DBCon.Close()
End Sub
and an example of how I query from a form
Private QC As New QueryControl
Public Sub RefreshGrid()
'RUN QUERY
QC.ExecQuery("SELECT * FROM Database")
If Not String.IsNullOrEmpty(QC.Exception) Then MsgBox(QC.Exception) : Exit Sub
'POPULATE DATAGRID
Datagridview1.DataSource = QC.DBDT
End Sub
I tried running the debugging the program while commenting each line individually and concluded that the ExecQuery() function is what causes the issue. Anyone met something similar?

I had the problem of the form resizing. It was the 32 bit Ace provider. I downloaded the 64 bit Ace provider and installed it. In the solution properties uncheck the prefer 32 bit box and all should be well.

Related

"No value given for one or more required parameters" error using OleDbCommand

I am trying to update a record in MS Access using VB.net. This code will be under the "Delivered" button. When I try to run it, it shows the "No value given for one or more required parameters" error. Here is my code:
Private Const strConn As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Users\Traicy\Downloads\MWL(11-30-2021)\MadeWithLove\MadeWithLove\MadeWithLove.mdb;"
ReadOnly conn As OleDbConnection = New OleDbConnection(strConn)
Dim cmd As OleDbCommand
Public Sub DeliveredUpdate()
Const SQL As String = "UPDATE DELIVERY SET delivery_status = #status"
cmd = New OleDbCommand(SQL, conn)
' Update parameter
cmd.Parameters.AddWithValue("#status", "Delivered")
' Open connection, update, then close connection
Try
conn.Open()
If cmd.ExecuteNonQuery() > 0 Then
MsgBox("The delivery status was successfully updated.")
End If
conn.Close()
Catch ex As Exception
MsgBox(ex.Message)
conn.Close()
End Try
End Sub
Do not declare connections or commands outside of the method where they are used. These database objects use unmanaged resources. They release these resources in their Dispose methods. The language provides Using blocks to handle this.
As mentioned in comments by Andrew Morton, you should have a Where clause to tell the database which record to update. This would contain the primary key of the record. I guessed at a name for the field, OrderID. Check your database for the real field name.
Access does not use named parameters but you can use names for readability. Access will be able to recognize the parameters as long as they are added to the Parameters collection in the same order that they appear in the sql string. In some databases the Add method is superior to AddWithValue because it doesn't leave the datatype to chance.
It is a good idea to separate your database code from your user interface code. If you want to show a message box in your Catch put the Try blocks in the UI code. This way your function can be used in a web app or mobile app without rewriting.
Public Function DeliveredUpdate(ID As Integer) As Integer
Dim recordsUpdated As Integer
Dim SQL As String = "UPDATE DELIVERY SET delivery_status = #status Where OrderID = #Id;"
Using conn As New OleDbConnection(strConn),
cmd As New OleDbCommand(SQL, conn)
cmd.Parameters.Add("#status", OleDbType.VarChar).Value = "Delivered"
cmd.Parameters.Add("#Id", OleDbType.Integer).Value = ID
conn.Open()
recordsUpdated = cmd.ExecuteNonQuery
End Using 'closes and disposes the command and connection
Return recordsUpdated
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim retVal As Integer
Dim id As Integer = 1 'not sure where you are getting this value from
Try
retVal = DeliveredUpdate(id)
Catch ex As Exception
MsgBox(ex.Message)
End Try
If retVal > 0 Then
MsgBox("The delivery status was successfully updated.")
End If
End Sub

SqlCommandBuilder Update Command is Generating Command but Not Updating Database From DataGridView

I am inheriting a form class (Form1) for Form 2. Form 1 code works without error. In Form 1, I use SqlCommandBuilder(SQL.DBDA).GetUpdateCommand to generate the update command for my Datagrid to pass to SQL data table which again works perfectly and the table is updated successfully. The SQL command text for Form 1 Update is shown here:
In Form 2, I write the following for the update command, where the only difference is Selecting the Table shown here:
SQL.ExecQuery("SELECT * FROM dtbRateVerse;")
SQL.DBDA.UpdateCommand = New SqlClient.SqlCommandBuilder(SQL.DBDA).GetUpdateCommand
MsgBox(SQL.DBDA.UpdateCommand.CommandText)
SQL.DBDA.Update(SQL.DBDT)
The command text for this update command is shown here:
It is not dissimilar to the successful update command shown in Form1 (image 1). Still, no data is passed to the SQL from the Gridview.
I also tried writing a dynamic Update statement without using the command builder shown below. The text of this statement generates an accurate SQL command but again, nothing passed to the database. This code is shown here:
For i = 1 To colEnd
colName.Add("[" & DataGridView1.Columns(i).HeaderText.ToString & "]")
Next
For i = 1 To colEnd
For y = 0 To Me.DataGridView1.RowCount - 1
For n = 1 To colEnd
gridVals.Add(DataGridView1.Rows(y).Cells(n).Value.ToString)
Next
With Me.DataGridView1
SQL.AddParam("#PrimKey", .Rows(y).Cells(0))
cmdUpdate = "UPDATE " & tbl_Name & " SET " & colName.Item(i - 1) & "=" & gridVals.Item(i - 1) & " WHERE ID=#PrimKey;"
SQL.ExecQuery(cmdUpdate)
End With
Next
Next
If anyone has any ideas/ solutions on what I need to do to get the update command working properly, I'd really appreciate it. Thanks!
Added ExecQuery methdod per request below:
Public Class SQLControl
Private DBConnect As New SqlConnection("SERVER STRING HERE")
Private DBCmd As SqlCommand
'DB DATA
Public DBDA As SqlDataAdapter
Public DBDT As DataTable
'QUERY PARAMETERS
Public Params As New List(Of SqlParameter)
'QUERY STATISTICS
Public RecordCount As Integer
Public Exception As String
Public Sub New()
End Sub
'ALLOW CONNECTION STRING OVERRIDE
Public Sub New(ConnectionString As String)
DBConnect = New SqlConnection(ConnectionString)
End Sub
'EXECUTE QUERY SUB
Public Sub ExecQuery(Query As String)
'RESET QUERY STATS
RecordCount = 0
Exception = ""
Try
DBConnect.Open()
'CREATE DATABASE COMMAND
DBCmd = New SqlCommand(Query, DBConnect)
'LOAD PARAMS INTO DB COMMAND
Params.ForEach(Sub(p) DBCmd.Parameters.Add(p)) 'LAMBDA EXPRESSION
'CLEAR PARAMS LIST
Params.Clear()
'EXECUTE COMMAND & FILL DATASET
DBDT = New DataTable
DBDA = New SqlDataAdapter(DBCmd)
RecordCount = DBDA.Fill(DBDT)
Catch ex As Exception
'CAPTURE ERROR
Exception = "ExecQuery Error: " & vbNewLine & ex.Message
Finally
'CLOSE CONNECTION
If DBConnect.State = ConnectionState.Open Then DBConnect.Close()
End Try
End Sub
'ADD PARAMS
Public Sub AddParam(Name As String, Value As Object)
Dim NewParam As New SqlParameter(Name, Value)
Params.Add(NewParam)
End Sub
'ERROR CHECKING
Public Function HasException(Optional Report As Boolean = False) As Boolean
If String.IsNullOrEmpty(Exception) Then Return False
If Report = True Then MsgBox(Exception, MsgBoxStyle.Critical, "Exception:")
Return True
End Function
End Class
SQLControl class is the brain child of VBToolbox user. It is a good tool, once created, just simply use it and not have to worry about all complicated setups that normally associated with SQL data connection. Besides, it makes the solution much cleaner & simpler.
There is no need to modify SQLControl class, when you need to update the changes, simply:
'SAVE UPDATES TO DATABASE
SQL.DBDA.UpdateCommand = New SqlClient.SqlCommandBuilder(SQL.DBDA).GetUpdateCommand 'Need primary key in SEL statement
SQL.DBDA.Update(SQL.DBDT)
and refresh the DataGridview afterwards.
The issue appears to be as I suspected it was. Here's for code from the form:
SQL.ExecQuery("SELECT * FROM dtbRateVerse;")
SQL.DBDA.UpdateCommand = New SqlClient.SqlCommandBuilder(SQL.DBDA).GetUpdateCommand
SQL.DBDA.Update(SQL.DBDT)
In that, you first call ExeQuery and finally call Update on the data adapter and pass the DBDT DataTable. In your ExecQuery method, you have this:
DBDT = New DataTable
DBDA = New SqlDataAdapter(DBCmd)
RecordCount = DBDA.Fill(DBDT)
That means that the first code snippet is going to be calling Update and passing a DataTable that was just freshly created and populated. Why would you expect that DataTable to have any changes in it to save? This is an example of why I think DAL classes like this are garbage.
If you're going to be using a class like that then you should be creating a command builder inside it when you create the data adapter, e.g.
'DB DATA
Public DBDA As SqlDataAdapter
Public DBCB As SqlCommandBuilder
Public DBDT As DataTable
and:
'EXECUTE COMMAND & FILL DATASET
DBDT = New DataTable
DBDA = New SqlDataAdapter(DBCmd)
DBCB = New SqlCommandBuilder(DBDA)
RecordCount = DBDA.Fill(DBDT)
Now there's no need to call ExecQuery again or create your own command builder. Just call ExecQuery once when you want the data, get the populated DataTable, use it and then call Update on the data adapter and pass that DataTable when it's time to save.
That said, you don't even necessarily need to change that class. If you already have an instance and you already called ExecQuery then it already contains the data adapter and the DataTable. You can still create your own command builder if you want but just don't call ExecQuery again and lose the objects you already had. If you changed that first code snippet to this:
Dim commandBuilder As New SqlClient.SqlCommandBuilder(SQL.DBDA)
SQL.DBDA.Update(SQL.DBDT)
Then it would work, assuming that you are using the same SQLControl instance as you used to get the data in the first place.

Autofill textboxes from access database based on one text box value

I have 3 text boxes and based on Customer Name suggestion other 2 boxes should be auto filled
I am new to database so I am finding codes on internet and trying to implement it but it
is not working. So please help me with this.
I am working in for Windows Form Application in visual basic.
Private Sub AutoComplete()
'sql = "Select * FROM Table1 where CustomerName='" & custnm.Text & "'"
com = New OleDbCommand(sql, con)
reader = com.ExecuteReader()
Dim autoComp As New AutoCompleteStringCollection()
While reader.Read()
autoComp.Add(reader("CustomerName"))
autoComp.Add(reader("ContactNO"))
End While
reader.Close()
custnm.AutoCompleteMode = AutoCompleteMode.Suggest
custnm.AutoCompleteSource = AutoCompleteSource.CustomSource
custnm.AutoCompleteCustomSource = autoComp
contactno.AutoCompleteMode = AutoCompleteMode.Suggest
contactno.AutoCompleteSource = AutoCompleteSource.CustomSource
contactno.AutoCompleteCustomSource = autoComp
wcontno.AutoCompleteMode = AutoCompleteMode.Suggest
wcontno.AutoCompleteSource = AutoCompleteSource.CustomSource
wcontno.AutoCompleteCustomSource = autoComp
End Sub
This is only a partial solution to straighten out your database code. It is too much code to put in a comment.
Keep your database objects local so you can control when they are closed and disposed. Objects that expose a .Dispose method may have unmanaged resources that need to be released. A Using...End Using block will handle this for you even if there is and error.
Always use Parameters to avoid sql injection which can damage your database. Also it makes the sql string easier to write. You will need to check your database for the proper datatype because I had to guess.
A DataReader uses an open connection until it is done. You don't want to hold your connection open while you use the data so I chose a DataTable. Just load it and pass it off to where you want to use it. The connection and command are closed and disposed.
I wasn't sure why you combined the data in a single AutoCompleteStringCollection. It seems that one for each combo box would be better. They are form level variables so you can used them in another method to attach to a combo box.
I think you need to look into BindingSource and BindingNavigator to sync the combo boxes.
Private Function GetData() As DataTable
Dim dt As New DataTable
Using con As New OleDbConnection("Your connection string"),
com As New OleDbCommand("Select * FROM Table1 where CustomerName= #Name;", con)
com.Parameters.Add("#Name", OleDbType.VarChar).Value = custnm.Text
con.Open()
dt.Load(com.ExecuteReader)
End Using
Return dt
End Function
Private autoCompName As New AutoCompleteStringCollection()
Private autoCompNO As New AutoCompleteStringCollection()
Private Sub FillAutoCompleteCollection()
Dim dt = GetData()
For Each row As DataRow In dt.Rows
autoCompName.Add(row("CustomerName").ToString)
autoCompNO.Add(row("ContractNO").ToString)
Next
End Sub

using loop to hide ribbon controls vb.net

I have a table for user screens.
UserID - ScreenID - Perm
I need to hide controls when the form opens with UserID and ScreenID, and I can't use the loop with condition.
This is my code:
Try
Dim da As New SqlDataAdapter
Dim ds As New DataSet
If dbconnect.State = ConnectionState.Closed Then dbconnect.Open()
da = New SqlDataAdapter("SELECT * FROM UserScreens WHERE UserID='" & UserID & "'", dbconnect)
da.Fill(ds, "Table")
If ds.Tables(0).Rows.Count > 0 Then
Dim M As DataRow = ds.Tables(0).Rows(0)
For Each ctrl As Control In Ribbon1.Controls
ctrl.Visible = M.Item("Perm")
Next
'DocIDtxt.Text = M.Item("DOCID")
End If
Catch ex As Exception
If dbconnect.State = ConnectionState.Open Then dbconnect.Close()
MsgBox(ex.ToString)
End Try
I always worry about connections first. From MS docs
"The IDbConnection object associated with the select command must be valid, but it does not need to be open. If the IDbConnection is closed before Fill is called, it is opened to retrieve data, then closed. If the connection is open before Fill is called, it remains open."
What that means is you opened your connection before the .Fill method so it will remain open. Bad! The only way your code closes the connection is if there is an error. A Finally section with .Close in your Try...End Try would do the trick but better yet use Using...End Using. This block will ensure that your objects are closed and disposed properly even if there is an error. When an object has a Dispose method it may have unmanaged resources that it must clean up.
Next, turn on Option Strict. It will be a great help pointing potential runtime errors.
Third, always use Parameters.
See inline explanation and comments.
Private Sub OpCode()
Try
'A DataTable does not have a Dispose() method so it will fall
'out of scope and be garbage collected.
Dim dt As New DataTable
'SqlConnection has a Dispose() method so use Using
Using cn As New SqlConnection("Your connection string")
'SqlCommand has a Dispose() method so use Using
Using cmd As New SqlCommand("SELECT * FROM UserScreens WHERE UserID= #UserID", cn)
'Always use Parameters. Never concatenate strings in SQL statements.
cmd.Parameters.Add("#UserID", SqlDbType.VarChar).Value = UserID
cn.Open()
'SqlDataReader has a Dispose() method so use Using
Using dr As SqlDataReader = cmd.ExecuteReader
dt.Load(dr)
End Using
End Using
End Using
If dt.Rows.Count > 0 Then
Dim M As DataRow = dt.Rows(0)
For Each ctrl As Control In Ribbon1.Controls
'If Perm is not a boolean this line of code can't work
ctrl.Visible = CBool(M.Item("Perm"))
Next
'DocIDtxt.Text = M.Item("DOCID")
End If
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub

how to populate items from database in a listbox in vb.net

I was developing an application using oop concept.I have a class that has 2 attributes and have Get and Set methods namely WorkItemNumber and Description.
On the client side i have a list box used to populate the work items based on their description.Here's the code i wrote in the class o read items from the database.
Public Sub LoadWorkItem()
' Load the data.
' Select records.
Dim oWorkItem As WorkItem = New WorkItem()
Dim conn As New OleDbConnection
Dim data_reader As OleDbDataReader
conn = oWorkItem.GetDbConnection()
Dim cmd As New OleDbCommand("SELECT * FROM work_item ORDER BY [work item number]", conn)
data_reader = cmd.ExecuteReader()
'ListBox1.Items.Clear()
If data_reader.HasRows = True Then
Do While data_reader.Read()
WorkItemNumber = data_reader.Item("work item number")
Description = data_reader.Item("description")
Loop
End If
data_reader.Close()
data_reader = Nothing
cmd.Dispose()
cmd = Nothing
conn.Close()
conn.Dispose()
End Sub
How do i populate the listbox using the code,and if there's any improvement on the code please do tell me as well.Thank you
To poulate your ListBox, do this...
ListBox1.Item.Clear()
If data_reader.HasRows Then
Do While data_reader.Read()
WorkItemNumber = data_reader.Item("work item number")
Description = data_reader.Item("description")
ListBox1.Items.Add(New ListItem(Description, WorkItemNumber)
Loop
End If
As far as improvements, start by using a Using statement for the DB connection. In your code, if there is an exception while the database connection is open, it will never get closed. This is better...
Using conn As OleDbConnection = oWorkItem.GetDbConnection()
' Execute SQL and populate list...
End Using
The above code assures that your connection will be closed.
Then, turn on Option Strict and Option Explicit. This will force you to declare the Type for Description and WorkItemNumber and cast them as Strings when adding a ListItem. This will reduce run-time errors.
Finally, if this is anything but a small app you are doing as a learning experiment, you should read up on tiered application design. Your code is mixing UI, business logic, and data access in the same method. This is generally frowned upon.
Your "user interface" LoadWorkItem() method should ask a "core" method for a list of WorkItems.
Your core method should then ask a "data access" method for data.
The "data access" method should make the call to the database.
Happy coding.
Update: You can find excellent info about n-Tier architecture on MSDN. A good book to read once you grasp the fundamentals and have some confidence in .NET is Visual Basic .NET Business Objects.
Imports System.Data.SqlClient 'Reference The Sql Client
Public Class Form1
''Make sure to change the connection string below to your connection string this code only works for SQL DataBase. If Your connection String is wrong This will Not Work
Dim connString As String = "Data
Source=NameofYourSQLServer\SQLEXPRESS;Initial Catalog=NameOfYourDataBase;Integrated Security=True"
Dim tblDIV As DataTable
Dim daDIV As SqlDataAdapter
Dim dsDIV As New DataSet
Dim oCon As SqlConnection
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim oCon = New SqlConnection
oCon.ConnectionString = connString
dsDIV = New DataSet
' Select all Fields and order by ID or Replace * with name of Field
daDIV = New SqlDataAdapter("SELECT * FROM NameOfYourTable ORDER BY Id DESC", oCon)
'*** Define command builder to generate the necessary SQL
Dim builder As SqlCommandBuilder = New SqlCommandBuilder(daDIV)
builder.QuotePrefix = "["
builder.QuoteSuffix = "]"
Try
daDIV.FillSchema(dsDIV, SchemaType.Source, "DIV")
daDIV.Fill(dsDIV, "DIV")
tblDIV = dsDIV.Tables("DIV")
ListBox1.DataSource = tblDIV
ListBox1.DisplayMember = "NameOfTheFieldYouWanttoDisplay"
Catch ex As Exception
MsgBox("Encountered an Error;" & vbNewLine & ex.Message)
oCon.Close()
End Try
End Sub