List View Population Errors - vb.net

I have 3 columns that need populating when a user presses 'search' however every time that I click 'search' only the Employee ID appears, neither the 'First Name' nor the 'Last Name' are present in the List View. The data does exist in my Access Database, this is proved as the program produces a blank record instead of a null value error. The code that I am using to populate the List View is:
ds.Clear()
lstClockin.Items.Clear()
con.ConnectionString = provider & datafile
con.Open() 'Open connection to the database
sqlstatement = "SELECT * FROM [EmployeeAccounts]"
da = New OleDb.OleDbDataAdapter(sqlstatement, con)
da.Fill(ds, "allmembers") 'Fill the data adapter
con.Close()
Dim recordCount, x As Short
recordCount = 0
x = 0
recordCount = ds.Tables("allmembers").Rows.Count
With ds.Tables("allmembers")
Do Until x = recordCount
lstClockin.Items.Add(.Rows(x).Item(0))
lstClockin.Items(x).SubItems.Add(.Rows(x).Item(1))
lstClockin.Items(x).SubItems.Add(.Rows(x).Item(2))
lstClockin.Items(x).SubItems.Add(.Rows(x).Item(3))
x = x + 1
Loop
End With
The first 3 columns in the Database are, [Employee ID], [First Name] & [Last Name]
Any suggestions are welcome; however I have ruled out using a DataGridView or any control. As this program needs to use a ListView. Thankyou in advance!

There are several things that can be improved in the code:
Dim SQL = "SELECT Id, Name, Fish FROM Sample"
Using dbcon As New OleDbConnection(ACEConnStr)
Using cmd As New OleDbCommand(SQL, dbcon)
dbcon.Open()
Dim lvi As ListViewItem
myLV.SuspendLayout()
Using rdr = cmd.ExecuteReader
Do While rdr.Read
lvi = New ListViewItem(rdr.GetInt32(0).ToString)
If rdr.IsDBNull(1) Then
lvi.SubItems.Add("")
Else
lvi.SubItems.Add(rdr.GetString(1))
End If
If rdr.IsDBNull(2) Then
lvi.SubItems.Add("")
Else
lvi.SubItems.Add(rdr.GetString(2))
End If
myLV.Items.Add(lvi)
Loop
End Using
myLV.ResumeLayout()
End Using
End Using
Connections and other DB Provider objects allocate resources which need to be released or your app will leak. Using blocks for things that implement Dispose will close and dispose of them for you
There is no need for an DataAdapter, DataSet and DataTable since you are copying the data to the control. This code uses a DataReader to get the data.
Rather than SELECT * the query specifies the columns/order so it can use the Getxxxxx methods to get typed data. That doesnt matter a great deal in this case because everything gets converted to string for the ListView. lvi.SubItems.Add(rdr(COLUMN_NAME).ToString()) would also work.
It seems unlikely the ID column could be null, so the code only checks the other 2 for DbNull (another thing the DGV can handle without help).
Since the ListView is suboptimal and slow in adding items, SuspendLayout and ResumeLayout are used to minimize paints while it is populated.
I am not at all sure what ...the program produces a blank record means, but in order use a ListView like it is a grid, the View property must be Details and you have to have added 3 columns in the IDE (or manually create them in code). Nothing will show without those settings.
If the DataTable is needed/used elsewhere, you can still fill one without a DataAdpater and populate the LV from it:
...
dt.Load(cmd.ExecuteReader)
For Each row As DataRow In dt.Rows
lvi = New ListViewItem(row(0).ToString())
If DBNull.Value.Equals(row(1)) Then
lvi.SubItems.Add("")
Else
lvi.SubItems.Add(row(1).ToString())
End If
If DBNull.Value.Equals(row(2)) Then
lvi.SubItems.Add("")
Else
lvi.SubItems.Add(row(2).ToString())
End If
myLV.Items.Add(lvi)
Next
This uses a different DBNull check since it is using a DataRow and not the DataReader.

Related

Getting "Database is Locked" when trying to move a list of records from one table to another table in SQLite

I have a Public Sub to move a collection of records from one table to another in the same SQLite database. First it reads a record from strFromTable, then writes it to strToTable, then deletes the record from strFromTable. To speed things up, I've loaded the entire collection of records into a transaction. When the list involves moving a lot of image blobs, the db gets backed up, and throws the exception "The Database is Locked". I think what is happening is that it's not finished writing one record before it starts trying to write the next record. Since SQLite only allows one write at a time, it thows the "Locked" exception.
Here is the code that triggers the error when moving a lot of image blobs:
Using SQLconnect = New SQLiteConnection(strDbConnectionString)
SQLconnect.Open()
Using tr = SQLconnect.BeginTransaction()
Using SQLcommand = SQLconnect.CreateCommand
For Each itm As ListViewItem In lvcollection
SQLcommand.CommandText = $"INSERT INTO {strToTable} SELECT * FROM {strFromTable} WHERE id = {itm.Tag}; DELETE FROM {strFromTable} WHERE ID = {itm.Tag};"
SQLcommand.ExecuteNonQuery()
Next
End Using
tr.Commit()
End Using
End Using
When I get rid of the transaction, it executes without error:
Using SQLconnect = New SQLiteConnection(strDbConnectionString)
SQLconnect.Open()
Using SQLcommand = SQLconnect.CreateCommand
For Each itm As ListViewItem In lvcollection
SQLcommand.CommandText = $"INSERT INTO {strToTable} SELECT * FROM {strFromTable} WHERE id = {itm.Tag}; DELETE FROM {strFromTable} WHERE ID = {itm.Tag};"
SQLcommand.ExecuteNonQuery()
Next
End Using
End Using
I'm not very good with DB operations, so I'm sure there is something that needs improvement. Is there a way to make SQLite completely finish the previous INSERT before executing the next INSERT? How can I change my code to allow using a transaction?
Thank you for your help.
.
Ok ... here is the solution that I decided to go with. I hope this helps someone finding this in a search:
Dim arrIds(lvcollection.Count - 1) As String
Dim i as Integer = 0
' Load the array with all the Tags in the listViewCollection
For i = 0 to lvcollection.Count - 1
arrIds(i) = lvcollection(i).Tag 'item.Tag holds the Primary Key "id" field in the DB
Next
'build a comma-space separated string of all ids from the array of ids.
Dim strIds as String = String.Join(", ", arrIds)
Using SQLconnect = New SQLiteConnection(strDbConnectionString)
SQLconnect.Open()
Using tr = SQLconnect.BeginTransaction()
Using SQLcommand = SQLconnect.CreateCommand
SQLcommand.CommandText = $"INSERT INTO {strToTable} SELECT * FROM {strFromTable} WHERE id IN ({strIds});"
SQLcommand.ExecuteNonQuery()
SQLcommand.CommandText = $"DELETE FROM {strFromTable} WHERE ID IN ({strIds});"
SQLcommand.ExecuteNonQuery()
End Using
tr.Commit()
End Using
End Using
The IN statement allows me to pass all of the "id" values to be deleted as a batch. This solution is faster and more secure than doing them one by one with no transaction.
Thanks for the comments, and best wishes to everyone in their coding.

Naming Column Header Based On Results From Database

net and would to have the Header Text of columns in a datagridview be named after results from the database, e.g the query in my code returns four dates,30/08/2017,04/09/2017,21/09/2017 and 03/02/2018. My aim is to have the column headers in the data grid named after those dates. Your help will highly be appreciated.
sql = "SELECT COUNT (ServiceDate) As NoOfServiceDates FROM (SELECT DISTINCT ServiceDate FROM tblattendance)"
Using command = New OleDbCommand(sql, connection)
Using reader = command.ExecuteReader
reader.Read()
ColumnNo = CInt(reader("NoOfServiceDates")).ToString
End Using
End Using
DataGridView1.ColumnCount = ColumnNo
For i = 0 To DataGridView1.Columns.Count - 1
sql = "SELECT DISTINCT ServiceDate FROM tblattendance"
Using command = New OleDbCommand(sql, connection)
Using reader = command.ExecuteReader
While reader.Read
DataGridView1.Columns(i).HeaderText = reader("ServiceDate").ToString
End While
End Using
End Using
Next
The current code re-runs the query each time through the column count loop, meaning it will set the column header for that column to all of the date values in sequence, so the last value in the query shows in the all the columns. You only need to run the query once:
Dim i As Integer = 0
sql = "SELECT DISTINCT ServiceDate FROM tblattendance"
Using command As New OleDbCommand(sql, connection), _
reader As OleDbDatareader = command.ExecuteReader()
While reader.Read
DataGridView1.Columns(i).HeaderText = reader("ServiceDate").ToString
i+= 1
End While
End Using
Additionally, this still results in two separate trips to the database, where you go once to get the count and again to get the values. Not only is this very bad for performance, it leaves you open to a bug where another user changes your data from one query to the next.
There are several ways you can get this down to one trip to the database: loading the results into memory via a List or DataTable, changing the SQL to include the count and the values together, or adding a new column each time through the list. Here's an example using the last option:
DataGridView1.Columns.Clear()
Dim sql As String = "SELECT DISTINCT ServiceDate FROM tblattendance"
Using connection As New OleDbConnection("string here"), _
command As New OleDbCommand(sql, connection)
connection.Open()
Using reader As OleDbDataReader = command.ExecuteReader()
While reader.Read
Dim column As String = reader("ServiceDate").ToString()
DataGridView1.Columns.Add(column, column)
End While
End Using
End Using
Even better if you can use something like Sql Server's PIVOT keyword in combination with the DataGridView's AutoGenerateColumns feature for DataBinding, where you will write ONE SQL statement that has both column info and data, and simply bind the result set to the grid.
The For Next is incorrect. You execute your command for every column, when you only need to execute it once. The last result from the DataReader will be the header for every column as currently written.
You should iterate through your DataReader and increment the cursor variable there:
Dim i As Integer = 0
Using command = New OleDbCommand(sql, connection)
Using reader = command.ExecuteReader
While reader.Read
DataGridView1.Columns(i).HeaderText = reader("ServiceDate").ToString
i += 1
End While
End Using
End Using

SqlDataAdapter.Fill(DataGridView.DataSource) duplicates all rows

Simple question:
When I call SqlDataAdapter.Fill(DataGridView.DataSource) the second time after initially creating first Data it does not update the contained rows. It simply adds all rows returned by the select command to my DataGridView.
If I call it a third, fourth (so on) it will also just add the returned rows.
Am I understanding the .Fill(DataTable) function wrong? How do I update the already existing DataTable correctly? Which line of code is responsible for that?
Turns out it has to be a code problem;
DataGridView1.AutoGenerateColumns = False
Dim sql = "select * from myTable"
oDtSource = New DataTable
oAdapter = New SqlDataAdapter
oCon = sqlCon("serverName\Instance", "myDataBase") ' Returns a SqlConnection
oCmd = New SqlCommand(sql, oCon)
oCon.Open()
oDtSource.Clear()
oAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey
oAdapter.SelectCommand = oCmd
oAdapter.Fill(oDtSource)
DataGridView1.DataSource = oDtSource
For refreshing I use oAdapter.Fill(oDtSource)
The PrimaryKey is set in the database
From MSDN:
You can use the Fill method multiple times on the same DataTable. If a
primary key exists, incoming rows are merged with matching rows that
already exist. If no primary key exists, incoming rows are appended to
the DataTable.
So either define a primary key or clear the table first.
Dim table = CType(DataGridView.DataSource, DataTable)
table.Clear()
' fill ...
To define primary key(s) manually read this. To let it create automatically if they are defined in the database you need to set the MissingSchemaAction to AddWithKey:
' ...
dataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey
' fill ...
The edit code doesnt show the PrimaryKey being defined for the DataTable. This will configure the DataAdapter to perform updates and enabled refreshing the DataTable. The code uses MySQL but the Provider objects all work the same in this regard:
' persistant form level vars
Private daSample As MySqlDataAdapter
Private dtSample As DataTable
...
Elsewhere:
' there are caveats with WHERE clauses
Dim sql = "SELECT Id, Name, Country, Animal FROM SAMPLE WHERE Color = 'onyx'"
' using the ctor overload, no need for a DbCommand or Connection object
daSample = New MySqlDataAdapter(sql, MySQLConnStr)
' initialize the CommandBuilder, get other commands
Dim cbSample = New MySqlCommandBuilder(daSample)
daSample.UpdateCommand = cbSample.GetUpdateCommand
daSample.InsertCommand = cbSample.GetInsertCommand
daSample.DeleteCommand = cbSample.GetDeleteCommand
dtSample = New DataTable()
daSample.FillSchema(dtSample, SchemaType.Source)
dtSample.PrimaryKey = New DataColumn() {dtSample.Columns("id")}
daSample.Fill(dtSample)
dgv1.DataSource = dtSample
To pick up changes made to the db from other client apps:
daSample.Fill(dtSample)
Initial display:
After I change a row to "onyx" from a UI browser and Update the changed row shows up:
WHERE clauses can be a bit of an issue. Since it restricts the subset of data pulled back, Update is only going to compare rows in the new result set. So, if I change an onlyx row to "blue" it wont be removed.
One solution is to use .DefaultView.RowFilter on the table, but that can slow things down since it requires returning all rows to the client to be filtered there. Its not perfect.

Filter databound combobox based on value in another column from the same dataset

I am attempting to create a process scheduler as a project in school this summer and am quite new to using VB. Currently I have a combobox bound to the "EmployeeBindingSource" which holds information for every single employee in a company. Each employee is assigned a skill level for each work station in the building. What I would like to do is if an employee is listed as having "0" skill in say, the assembly station, when selecting an employee to assign to the assembly station, that particular employee will not appear in the combobox.
From doing some research I believe that rather than binding the combobox to the data source in the designer, I will have to assign values to the each combo box in the code on a form load.
This is the access table that holds all the employee information
In the table we can see that Steve has a skill level of "0" in the AS_Level category (assembly).
However, here we can see that he still appears as an option for the assembly area when creating a new project
Currently all the data binding happens in the designer for each combo box and therefore no code has been written for the data binding. At the moment each combo box is bound to the "Full_Name" column in the access table.
Again, I am quite new to VB so I apologize if this is too vague. Let me know if I can give any more helpful information. Thank you all in advance.
After adding the code suggested here is what I have
provider = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source ="
datafile = "C:\Users\Jacob\Desktop\Halton\HaltonProject.accdb"
connString = provider & datafile
myConnection.ConnectionString = connString
myConnection.Open()
Dim str As String
str = "SELECT * FROM [Employee] WHERE (SP_Level <> '" & 0 & "')"
Dim cmd As OleDbCommand = New OleDbCommand(str, myConnection)
Dim dr As OleDbDataReader = cmd.ExecuteReader
Dim userfound As Boolean = False
Dim Full_Name As String
While dr.Read
userfound = True
Full_Name = dr("Full_Name").ToString
SP_Emp1.Items.Add(Full_Name)
SP_Emp2.Items.Add(Full_Name)
SP_Emp3.Items.Add(Full_Name)
SP_Emp4.Items.Add(Full_Name)
End While
myConnection.Close()
Works completely now!
Based on your comments and post, I assume, the checkboxes on the left enable/disable the comboboxes on that row, and since you're binding them individually if you change your query so it looks like this:
Dim cmd As OleDbCommand = New OleDbCommand("SELECT * FROM [Employee] WHERE AS_Level <> 0 , myConnection) Dim dr As OleDbDataReader = cmd.ExecuteReader
Note that the where clause will vary depending on your needs, so it could be WHERE AS_Level <> 0 or WHERE SP_Level <> 0 and so on.
I have never used an Ole database so I'm not quite sure about the syntax, hope this helps.

Vb.net pull in a SQL table row by row

I am a little new to using vb.net and SQL so I figured I would check with you guys to see if what I am doing makes sense, or if there is a better way. For the first step I need to read in all the rows from a couple of tables and store the data in the way the code needs to see it. First I get a count:
mysqlCommand = New SQLCommand("SELECT COUNT(*) From TableName")
Try
SQLConnection.Open()
count = myCommand.ExecuteScalar()
Catch ex As SqlException
Finally
SQLConnection.Close()
End Try
Next
Now I just want to iterate through the rows, but I am having a hard time with two parts, First, I cannot figure out the SELECT statement that will jet me grab a particular row of the table. I saw the example here, How to select the nth row in a SQL database table?. However, this was how to do it in SQL only, but I was not sure how well that would translate over to a vb.net call.
Second, in the above mycommand.ExecuteScalar() tell VB that we expect a number back from this. I believe the select statement will return a DataRow, but I do not know which Execute() statement tells the script to expect that.
Thank you in advance.
A simple approach is using a DataTable which you iterate row by row. You can use a DataAdapter to fill it. Use the Using-statement to dispose/close objects property that implement IDisposable like the connection:
Dim table = New DataTable
Using sqlConnection = New SqlConnection("ConnectionString")
Using da = New SqlDataAdapter("SELECT Column1, Column2, ColumnX FROM TableName ORDER By Column1", sqlConnection)
' you dont need to open/close the connection with a DataAdapter '
da.Fill(table)
End Using
End Using
Now you can iterate all rows with a loop:
For Each row As DataRow In table.Rows
Dim col1 As Int32 = row.Field(Of Int32)(0)
Dim col2 As String = row.Field(Of String)("Column1")
' ...'
Next
or use the table as DataSource for a databound control.