VB.NET Clear DataTable then Fill AFTER Successfully Getting New Data - vb.net

I currently use a TableAdapter to fill() a typed datatable in a typed dataset. ClearBeforeFill is set to True as the underlying data is different each time. The problem is that in the case the database is unreachable, the old data gets cleared before it knows. I want the old data to stay in the datatable (and subsequently stay displayed) in the case an error occurs retrieving from the database. Ideally, I'd still use a tableAdapter and the GetData() method instead of the Fill() method but I can't seem to figure out how to replace the current datatable with the one returned by GetData(). Tables.Remove() then Tables.Add() doesn't seem to work. Below code gives an error: "'table' argument cannot be null." & vbCrLf & "Parameter name: table"
Dim TempTbl As DS_ERecord.DT_spRefreshAndSelectStepConnectorMonitorDataTable
TempTbl = TAi_spRefreshAndSelectStepConnectorMonitor.GetData(ForceRefresh, CmbMonitorView.SelectedValue)
DSi_ERecord.Tables.Remove(DSi_ERecord.DT_spRefreshAndSelectStepConnectorMonitor)
DSi_ERecord.Tables.Add(TempTbl)

Related

VB.net - SQLite query response turning empty after first interaction

so I'm using SQLite in a VB.net project with a populated database. I'm using the Microsoft.Data.Sqlite.Core and System.Data.SQLite NuGet package libraries. So the problem presents when I'm trying to get the result of a query. At first the SQLiteDataReader gets the response and all the elements of the desired table. I know this cause in the debugger I have a breakpoint after the setting the object and when I check the parameters of the SQLiteDataReader object the Result View shows all the elements of my table, but as soon as I remove the mouse from the object and check it again the Result View turns out empty, without even resuming with the next line of code. Does anyone know if its a known bug or something cause Ive used this exact method of querying a table in another project and it works.
The code:
Public Function RunQuery(com As String)
If CheckConnection() Then
command.CommandText = com
Dim response As SQLiteDataReader
response = command.ExecuteReader
Dim len As Integer = response.StepCount
Dim col As Integer = response.FieldCount
Dim resp(len, col) As Object
For i = 0 To col - 1
Using response
response.Read()
For j = 0 To len - 1
resp(i, j) = response.GetValue(j)
Next
End Using
Next
Debugger with populated result view
Debugger with empty result view
edit: added the for loop to show that its not only on the debugger that the result view is empty. When using response.Read() it throws an exception "System.InvalidOperationException: 'No current row'"
As I have told you in the comment, a DataReader derived class is a forward only retrieval object. This means that when you reach the end of the records returned by the query, that object is not capable to restart from the beginning.
So if you force the debugger to enumerate the view the reader reaches the end of the results and a second attempt to show the content of the reader fails.
The other part of your problem is caused by a misunderstanding on how to work on the reader. You should loop over the Read result not using a StepCount property. This property as far as I know, is not standard and other data providers don't support it. Probably it is present in the SQLite Provider because for them it is relatively easy to count the number of records while other providers don't attempt do calculate that value for performance reasons.
However, there are other ways to read from the reader. One of them is to fill a DataTable object with its Load method that conveniently take a DataReader
Dim data As DataTable = New DataTable()
Using response
data.Load(response)
End Using
' Now you have a datatable filled with your data.
' No need to have a dedicated bidimensional array
A DataTable is like an array where you have Rows and Columns instead of indexes to iterate over.

Problems updating a database using vb.net, oledbdataadapter

After going over multiple questions/answers on Stackoverflow and other boards I'm still lost on why I can't update an Access database from a datatable. I'm trying to take data from a datatable and insert that data into an Access table if it is blank, and replace the table if it already has data. I can successfully replace the table, but the data from the datatable does not get added.
However, the method which I'm using does not appear to work. My datatable comes from a bound datagridsource and the Access layer is called like this:
ConnectedDB.UpdateTable(DBTable, bsDataSource.DataSource)
Where ConnectedDB is the Access Layer class, DBTable is the string containing the Access table name, and bsDataSource is the bound data. As you can see, I passed the .Datasource to turn it into a datatable.
Here is the original (pre-Jan 29th) section of my work to add the datatable back into the Access table:
Public Function UpdateTable(strTable As String, dgDataTable As DataTable) As Boolean
Dim DS As New DataSet
dgDataTable.TableName = strTable
DS.Tables.Add(dgDataTable)
Using OpenCon = New OleDb.OleDbConnection(strConnectionString)
Using DataAdapter As New OleDbDataAdapter("SELECT * FROM " & strTable, OpenCon)
Dim DBcmd As OleDbCommandBuilder = New OleDbCommandBuilder(DataAdapter)
DBcmd.QuotePrefix = "["
DBcmd.QuoteSuffix = "]"
DataAdapter.UpdateCommand = DBcmd.GetUpdateCommand()
Try
OpenCon.Open()
DataAdapter.Fill(DS.Tables(strTable))
If DataAdapter.Update(DS.Tables(strTable)) > 0 Then
Return True
Else Return False
End If
Catch exo As Exception
MessageBox.Show(exo.Message)
Return False
End Try
End Using
End Using
End Function
My function tries to update an existing Access table with the name represented as strTable with the information in the datatable, dgDataTable from a datagridview. Each run hits the update check > 0 and returns a false which means syntax wise it should be working (i.e. no error messages). I have traced the table and it has all the data it should have (so the information is getting passed correctly from the grid through the update commands). I was playing with applying it in a dataset but I'm not sure I really need that.
I was tracing the variables through the update method and I think I found out why it won't update but I'm not sure what to do about it. The query it comes up with is like this:
UPDATE [RtoC] SET [R] = ?, [C] = ?, [Type] = ?, [Unknown] = ? WHERE (([R] = ?) AND ([C] = ?) AND ([Type] = ?) AND ((? = 1 AND [Unknown] IS NULL) OR ([Unknown] = ?)))
The Access table name is RtoC with fields R, C, Type, and unknown.
I'm thinking the "?" are not getting filled in causing the query to just not apply data back to Access. I'm not sure though how to set those items.
EDIT 1/29/20: I used the code changes I and jmcihinney document below and it does insert the lines into the Access table. This edit alters the question to be more specific about what I'm am trying to do, and how the datatable is created. Hopefully this clears up some wording on my part and provides some basis for the alteration of the row state.
The issue is that the Fill method of that data adapter calls AcceptChanges on the DataTable after populating it, thus there are no changes to save when you call Update.
That call to Fill shouldn't be there anyway though, because you don't want to retrieve any data, just save changes. You've got a whole lot of pointless code there. It should look more like this:
Public Function UpdateTable(strTable As String, dgDataTable As DataTable) As Boolean
Using DataAdapter As New OleDbDataAdapter("SELECT * FROM " & strTable, strConnectionString)
Dim DBcmd As OleDbCommandBuilder = New OleDbCommandBuilder(DataAdapter)
DBcmd.QuotePrefix = "["
DBcmd.QuoteSuffix = "]"
Try
Return DataAdapter.Update(dgDataTable) > 0
Catch exo As Exception
MessageBox.Show(exo.Message)
Return False
End Try
End Using
End Function
I took another tack at manipulating the database and in looking that up, I found the answer provided by jmcilhinney back in 2014! [Bulk Insert From DataTable to Access Database
In a for each loop across the rows of my datatable I set this:
row.SetAdded()
If I was filling I would have done something like:
DataAdapter.AcceptChangesDuringFill = True
Before the Fill command.
Unless this method has changed or there is a better way, I'll mark the link as the answer.
Thanks jmcilhinney....twice!

Delete selected rows in datagridview from database

I'm trying to delete the rows which the user selects in a datagridview using SQL Server.
I have a datagridview that loads the contents of the database, in this case Username and Password.
Keep in mind that each username is unique in the database and so I should be able to delete each row using the unique username.
The SQL I currently have is DELETE FROM 'Users&Passwords' WHERE 'Username' = ? I'm not sure if this is entirely correct, however whenever I click QueryBuilder it seems to accept it.
The code I have to try and do this is as follows:
Private Sub btn_remove_Click(sender As Object, e As EventArgs) Handles btn_remove.Click
For Each row As DataGridViewRow In DataGridView1.SelectedRows
Dim usernametodelete As String = DataGridView1.SelectedRows(0).Cells(0).Value.ToString
'TextBox1.Text += usernametodelete
Me.Users_PasswordsTableAdapter.DeleteUser(usernametodelete)
Me.Users_PasswordsTableAdapter.Fill(Me.ManageUsersDataSet._Users_Passwords)
Next
End Sub
I would like that when the user clicks the Remove User(s) button the selected rows from the datagridview remove the rows from the database. Any help would be greatly appreciated.
You're getting slightly more involved than you need to. The set of steps you'd ideally take would be:
0) rename your table so it doesn't have an & character in it - it's just asking for trouble
1) Add your table to your dataset with something like this process: right click the design surface, new tableadapter, configure the connection string, set the query as SELECT * FROM Users WHERE Username LIKE #username, ensure other things are set, like whether you want generate dbdirect methods, and whether you want the dataset to refresh new values
2) In your data sources window (might not be showing by default, find it in on of visual studio's menus) drag and drop the Users node from the data soruces window, out onto the form. This will create a datagridview bound to the typed datatable, and also create a dataset, tableadapter, tableadaptermanager (not strictly needed; can delete), a bindingnavigator tool strip (again not strictly needed but has a handy pre-wired Save button on it) and a bindingsource. It will also pre-fill some code into the form_load event handler to fill the datatable
3) That's it - your form has a grid, bound to a datatable. That grid is capable of deleting rows - click the row header and press the delete button on the keyboard, row disappears. Click the save icon in the toolstrip, and the change is persisted to the database. The only thing you have to do is get data into the table in the first place. I gave a SQL that allows you to choose some usernames to load, but you can easily make it load the whole lot by changing the line of code in Form_Load to:
yourTableAdapterName.Fill(yourDatasetname.YourDatatableName, "%")
Passing a percent as the name is like a * wildcard in DOS. LIKE % will select all records. You can also, of course, leave the default provision (it will reference a textbox on the toolstrip) and instead run the program, put a % in the textbox on the toolstrip and click Fill. Or you can put JOHNSMITH, or JOHN% in th textbox and fill, to load that user/those users respectively
Hopefully you already did 1 and 2..
A bit of a background story on how all this stuff works:
DataTables are client side representations of tables in the database. It isn't intended that they contain all the data in the database, only a portion of this that you want to work with. When you load a row from the DB you can edit it, delete it (mark as deleted) and when you call tableAdapter.Update(yourtable) th changes you made are persisted to the DB. Note that even though it's called Update, the method will save new rows (by doing an INSERT), deleted rows (SQL DELETE) and make updates (SQL UPDATE). If your datatable has 4 rows laodd from the DB, and you then add 2 new rows, make changes to 3 of the loaded rows and delete the 4th row, then calling tableadapter.Update will save all these changes to the DB. You aren't required to pick through your datatable, calling tableadapter.insert/update/delete accordingly, to manually persist these changes. The flow of using a tableadapter is thus:
tableAdapter.Fill(datatable, maybe,parameters,to,control,the,fill,here)
'work with the datatable here, change, insert, delete. Use loops/code, use the UI, etc
tableAdapter.Update(datatable) 'save changes
I suggest putting an id column in your datagridview and just hide it so that you can easily delete the record you want to remove easily without conflict, because if you will use a username, what if theres a duplicate username exist.
Private Sub BTNDELETE_Click(sender As Object, e As EventArgs) Handles BTNDELETE.Click
Public result As Integer = 0
Public cmd As New MySqlCommand
strcon.Open() 'your connection string here
With cmd
.Connection = strcon 'your connection string
.CommandText = "DELETE FROM `users` WHERE `id` = '" & DTGLIST.CurrentRow.Cells(0).Value.ToString() & "';"
result = cmd.ExecuteNonQuery
If result > 0 Then
MsgBox("Successfully Removed.", MsgBoxStyle.Information, "Info")
DTGLIST.Rows.Remove(DTGLIST.SelectedRows(0))
End If
End With
strcon.Close() 'close the connection string
End Sub
Managed to figure out why my delete query wasn't working a few days ago. It was due to my SQL not being correct. This worked for me DELETE FROM [Users&Passwords] WHERE (Username = ?).
Also having the line Me.Users_PasswordsTableAdapter.DeleteUser(usernametodelete) in my code made it work.

Error when trying to add a row to an already existing data table and grid view

I currently have a gridview that I am able to populate with a query. On a button click I would like to run the query again with a new value and add the new information to what is already displayed in the gridview. I am attempting to do this by copying the information that is currently in the gridview into a datatable then adding a row to the table then resetting the binding on the gridview and refreshing the gridview. The problem is I get an error when trying to add the new row (System.Data.NoNullAllowedException: 'ColumnName' does not allow nulls.
Here is the code I am trying to use for this process. Any help would be much appreciated.
Dim EpicorCM As New SqlCommand(JobSelect, EpicorCon)
EpicorCon.Open()
Dim EpicorReader As SqlDataReader = EpicorCM.ExecuteReader
Dim JobInfoTable As New DataTable
JobInfoTable = CType(dgvJobInfo.DataSource, DataTable).Copy
JobInfoTable.Rows.Add(EpicorReader)
dgvJobInfo.AutoGenerateColumns = True
dgvJobInfo.DataSource = JobInfoTable
dgvJobInfo.Refresh()
EpicorCon.Close()
You are trying to add a SqlDataReader to the Rows collection but that will not work. It only compiles because the Add() method has an overload that is params of Object.
Fortunately DataTable has a method to load a reader. Instead of this:JobInfoTable.Rows.Add(EpicorReader), do this:
JobInfoTable.Load(EpicorReader)
As an aside, commands and readers are Disposable resources so you can leak memory by not disposing of them either in Using blocks or in Finally blocks.

Need to Extend the Scope of a DataTable

I'm converting old VB 7 code which used ODBC to connect to an SQL Anywhere DB to VB 2013 and an Access 2010 DB.
I declare several DataSets at the top of the module but when I get into procedures and functions, I have lost the scope of the DataTable.
I have the following declared:
Public con As New System.Data.OleDb.OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\CLI_CRVM.accdb")
Public extractDA As New OleDbDataAdapter("SELECT * FROM [extract]", con)
Public extractCB = New OleDbCommandBuilder(extractDA)
Public extractDT As DataTable
Public extractDR As DataRow
Then, in a clicked event of a button, I call a procedure which loads the DataTable:
extractCB.quoteprefix = "["
extractCB.quotesuffix = "]"
extractDT = New DataTable
extractDA.Fill(extractDT)
When it returns to the clicked event code, it does a For Each loop:
For Each extractDR As System.Data.DataRow In extractDT.Rows
At this point, I can see values from the DataTable like this:
ls_plan_code = Trim(extractDR("plan_code"))
MsgBox("Plan Code: " & ls_plan_code)
But when I call a procedure or function where I need the values from the DataTable, they are no longer available. Ie. when this executes:
Sub accumulation(ByVal adec_premium As Decimal, ByVal ai_stage As Integer)
Dim ldec_mode As Decimal
ldec_mode = CDec(extractDR("pay_mode"))
End Sub
I get this error: "Object reference not set to an instance of an object."
I know a workaround is to pass the DataRow to the sub routine; however, there are several DataTables and many procedures and functions, some of which call other procedures and functions which rely on data from other DataTables. Additionally, some sub routines write values to an answer DataTable which then gets written to the Access DB.
So, while I know this may not be "proper" form, I've got to get this code up and running to test values and if there were a way - as it did in the VB 7 code - to get the scope of the DataTable to extend throughout the entire module, I think my problems will be solved.
Thanks in advance!
In the following line:
For Each extractDR As System.Data.DataRow In extractDT.Rows
By specifying extractDR As System.Data.DataRow, you are creating a new variable. This variable is being assigned the value instead of the variable with the same name in the higher scope.
Simply remove the As System.Data.DataRow:
For Each extractDR In extractDT.Rows