oledbdataadapter command builder getupdate, needs key for argument - vb.net

I seem to be having an issue. I have a dataset (excel) sourced to a datagridview in a windows forms application. I'm hoping to find a way to refresh/update my dataset within the datagridview after making a change to a cell.
Let me go through the steps I've taken to try and accomplish this. I've added a "refresh" button to the form and I've created a method called write2DGV which will write changes to the dataset after changes are made in the datagridview.
I've added a timer that will allow Excel to update to update the outputs. I've also added code to wipe clean my dataset (which is the datasource for the datagridview). Last I have a retrieve method that will source the dataset to the datagridview via the oledataadapter.
Unfortunately when I run the code I get an error message about not providing a key as an argument to the get update method. I'm not exactly sure what they mean by "key", also the only valid data types for the argument to the get update method is boolean. My code is below:
'Declarations
Dim myDataSet As DataSet
Dim MyCommand As OleDb.OleDbDataAdapter
Dim objWorkSheet As Excel.Worksheet = objExcel.ActiveSheet
Dim sizetimer As New System.Timers.Timer
Sub retrieveMyDataSet()
MyCommand = New OleDbDataAdapter(select * from [MyExcelWorksheet$A13:x150], MyConnection)
myDataSet = New System.Data.DataSet()
MyCommand.Fill(myDataSet)
myDataGridView.DataSource = myDataSet.Tables(0).DefaultView
End Sub
Sub write2Size()
'A-k gets written. Entire graph goes to a-x so I only need to writ the columncount -14
'x is letter 24. k is letter 11. 24-11 = 13. So, offset needs to be -13
'39;Rows 13 through first blank
Dim rowindex As Integer
Dim columnindex As Integer
For rowindex = 1 To myDataGridView.RowCount
For columnindex = 1 To myDataGridView.ColumnCount - 13
objWorkSheet.Cells(rowindex + 13, columnindex + 0) = myDataGridView(columnindex - 1, rowindex - 1).Value
Next
Next
End Sub
Sub refreshDGV()
write2DGV()
myDataSet.Clear()
Dim x As New OleDbDataAdapter("select * from [MyExcelWorksheet$A13:x150]", MyConnection)
Dim oledbCommands As New OleDb.OleDbCommandBuilder(x)
x.UpdateCommand = oledbCommands.GetUpdateCommand() 'oops type! removed = x.UpdateCommand
x.Update(myDataSet)
sizetimer.AutoReset = True
sizetimer.Interval = 2000 '2 seconds
retrieveMyDataSet()
End Sub
Private Sub refreshbtn_Click(sender As System.Object, e As System.EventArgs) Handles refreshbtnSize.Click
refreshDGV()
End Sub

Unfortunately when I run the code I get an error message about not
providing a key as an argument to the get update method.
The reason of this error is explained in this article that you should read carrefully: How To Use ADO.NET to Retrieve and Modify Records in an Excel Workbook With Visual Basic .NET
You are using one of the solution mentionned in this article to update the worksheet, ie Make changes to a DataSet that you have filled with a table/query from an Excel workbook and then call the Update method of the DataAdapter to resolve changes from the DataSet back to the workbook.
But just after we can read:
However, to use the Update method for change resolution you must set
parameterized commands for the DataAdapter's InsertCommand
INSERT INTO [Sheet1$] (F1, F2) values (?, ?)
and UpdateCommand:
UPDATE [Sheet1$]SET F2 = ? WHERE F1 = ?
Parameterized INSERT and UPDATE commands are
required because the OleDbDataAdapter does not supply key/index
information for Excel workbooks; without key/index fields, the
CommandBuilder cannot automatically generate the commands for you.
The message is clear: you have to explicitly write parameterized INSERT and UPDATE commands if you want achieve what you are trying to do.
And don't forget that Excel has a lot a functionnality but is not especially designed to work as a database. So to avoid a lot of problems, I recommand you to switch to another local database (SqlServerCompact, SQlLite, ...).

Related

How to check datagridview column already exists with same Header Text Before adding a new Column in VB.net

I am trying to add columns dynamically to data grid view using VB.net. But the issue is I need to check that the column name already exists. before adding it if exists I want it to be cancelled automatically. I am using another form to select the Job Nos. which will add as Datagridview Header Text when it saved. () below is the code I am using to add a column to my datagridview. Hope you understand the question. I found some nearby answers in C# unfortunately I am not able to convert those correctly as my C# coding knowledge is little weak.
Thank You!
Dim FRMP = FrmEReview.ReviewGrid.Columns
Dim NOHRS As New DataGridViewTextBoxColumn
FRMP.Add(NOHRS)
NOHRS.HeaderText = Me.Cmb_CName.Text & "-" & Me.Cmb_DName.Text
NOHRS.Width = 160
The obvious option - the one you should have been able to work out for yourself - is to simply loop through the columns and test the HeaderText of each one:
Dim headerText = $"{Cmb_CName.Text}-{Cmb_DName.Text}"
Dim headerTextFound = False
For Each column As DataGridViewColumn In FrmEReview.ReviewGrid.Columns
If column.HeaderText = headerText Then
headerTextFould = True
Exit For
End If
Next
If Not headerTextFound Then
'...
End If
This is basically the code equivalent of what you'd do manually, which is why you should have been able to do it for yourself, at least mostly.
The not-so-obvious solution for a beginner is to use LINQ. LINQ is basically a means to flatten loops like this, so it leads to far more succinct code:
Dim headerText = $"{Cmb_CName.Text}-{Cmb_DName.Text}"
If FrmEReview.ReviewGrid.
Columns.
Cast(Of DataGridViewColumn)().
All(Function(dgvc) dgvc.HeaderText <> headerText) Then
'...
End If

How to edit a record in an access database - visual basic

I want to edit a specific record in an access database but I keep on getting errors
this is the database I want to edit:
Access database
these are flashcards that the user has created and stored in an access database. What I want is that the user is able to edit the difficulty so it appears more/less often
This is the module:
Module Module1
Public Function runSQL(ByVal query As String) As DataTable
Dim connection As New OleDb.OleDbConnection("provider=microsoft.ACE.OLEDB.12.0;Data Source=flashcard login.accdb") 'Establishes connection to database
Dim dt As New DataTable 'Stores database in table called dt
Dim dataadapter As OleDb.OleDbDataAdapter
connection.Open() 'Opens connection
dataadapter = New OleDb.OleDbDataAdapter(query, connection)
dt.Clear() 'Clears datatable
dataadapter.Fill(dt) 'Fills datatable
connection.Close()
Return dt
End Function
End Module
And here is the button that the user can press to edit the database:
Private Sub btnSubmit_Click(sender As Object, e As EventArgs) Handles btnSubmit.Click
Dim sql As String
sql = "UPDATE flashcards set difficulty = '" & TxtDifficulty.Text
runSQL(sql)
End Sub
The difficulty column in the database should be able to be edited by the user through the value they entered in txtDifficulty.text
Good to hear I found the problem with the apostrophe.
I am going to need a where statement but the problem I have is that the user can create as much flashcards as they want so how would I write the where statement?
An INSERT statement does not have a WHERE clause but an UPDATE does and is usually by a primary key.
Look at how I add a new record ignoring mHasException and specifically using parameters. In this case a List is used but with little effort an array of DataRow can be passed instead.
Here is an example for updating a record with a DataRow.
To get other code samples for ms-access see the following repository.
In closing, in the above repository I don't get into all possibilities yet there should be enough there to get you going. And when reviewing code I flip between Add for adding a parameter and AddWithValue while Add is the recommend way but both are shown to see differences. see also Add vs AddWithValue.

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!

CommandText Property Has Not Been Initialized, retrieving data

Private Sub ButtonSubmitID_Click(sender As Object, e As EventArgs) Handles ButtonSubmitID.Click
Dim comm As New SqlCommand
Dim conn As New SqlConnection
conn.ConnectionString = "Data Source = localhost\SQLEXPRESS; Initial Catalog = test2Forms; Integrated Security = SSPI;"
comm.Connection = conn
Dim ID = TextBoxID.Text
comm.Parameters.AddWithValue("#ID", ID)
Dim adapter As SqlDataAdapter = New SqlDataAdapter(comm.CommandText, comm.Connection)
comm.CommandText = "SELECT * FROM withActivityLog3 WHERE ID = #ID"
Dim records As DataSet = New DataSet
adapter.Fill(records)
DataGridView2.DataSource = records
End Sub
CommandText property has not been initialized is the error I am receiving. I am able to pull all the data from the database into the GridView on the Form Load but when I try to narrow it down to one ID using a WHERE clause on the button trigger, it comes up with the above error. I've used the debugger to trace through one step at a time and the command and connection strings look correct. I've also successfully duplicated the query on my database using the SQL Server command line. I'm searching on a primary key (ID) so the expected results would be one uniquely identified row from the database.
As for the problem you know you have:
' initialize DataAdapter with (EMPTY) commandtext
Dim adapter As SqlDataAdapter = New SqlDataAdapter(comm.CommandText, comm.Connection)
' initialize Command Text
comm.CommandText = "SELECT * FROM withActivityLog3 WHERE ID = #ID"
When you pass the CommandText to the DataAdapter, it is empty because you havent set it yet which results in the error.
There is a fair amount of inefficiency in your code though. Rewritten:
' form level conn string
Private TheConnString As String = "Data Source = localhost\..."
Private Sub ButtonSubmitID_Click(sender ...
Dim dt As New DataTable
Using dbcon As New MySqlConnection(TheConnString)
Using cmd As New MySqlCommand("select * from Sample where Id = #id", dbcon)
cmd.Parameters.Add("#id", MySqlDbType.Int32).Value = Convert.ToInt32(TextBox2.Text)
dbcon.Open()
dt.Load(cmd.ExecuteReader)
dgvA.DataSource = dt
End Using
End Using
End Sub
Note: this uses MySQL but the concepts are the same for Sqlite, Access, SQL Server etc
There is no need to type or paste the connection string and over everywhere it is used. One form level variable will allow DRY (Dont Repeat Yourself) code.
Anything which implements the Dispose() method should be disposed of. That includes nearly all the DB Provider objects. The Using statement allows you to declare and initialize an object and at the End Using it is disposed of. Failing to Dispose of things can cause leaks and even run out of connections or resources to create things like DB Command objects.
There is no need to create a local DbDataAdapter. These are very powerful and useful critters meant to do much more than fill a DataTable. If that is all you are doing, you can use ExecuteReader method on the DbCommand object.
Nor do you need a local DataSet. Contrary to the name, these do not hold data, but DataTables. Since there is only one and it is local (goes out of scope when the method ends), you dont need a DataSet to store it.
The Add method should be used rather than AddWithValue. The code above specifies the datatype for the parameter so there is no guesswork required of the compiler. Of course with that comes the need to convert the text to a number...
...Since this is user input, you should not trust the user, so Integer.Tryparse would be more appropriate: I like pie will not convert to an integer. Data Validation is something you should do before you commence the DB ops.
Dim ID = TextBoxID.Text as used is pointless code. You do not need to move the textbox text into a new variable in order to use it. However, ID might be used to store the integer value

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