I'm trying to progress my programming abilities by coding in Visual Basic forms. I've created a database and linked that to a VB form, I'm now coding a way to write into the database instead of reading.
I'm filling an array which is in-turn put into the dataset row by row however when attempting to 'update' the table adapter I get the following error:
Update requires a valid UpdateCommand when passed DataRow collection with modified rows.
I believe this has something to do with primary keys, the table that my adapter is updating does NOT have a primary key as it's involved in a relationship with another table, containing a primary key. How can I update my table adapter without getting this error? Thanks.
The error message you've mentioned has nothing to do with primary keys on the table you are attempting to update, instead it's complaining because you haven't given your adapter a valid command which it can use to update the underlying table.
You'd hit the same error if you attempted to insert or delete new rows without specifying an insert or delete command on your adapter.
To solve the problem, initialise your UpdateCommand property to something meaningful, for example:
'Open connection
Using Conn as new OleDbConnection("connection string")
'This is the command that will update your table in the database
Using UpdateCmd = Conn.CreateCommand()
UpdateCmd.CommandText = "update yourtable set col1 = ?, col2 = ?"
'Create a parameter to pass in the value of the "col1" column on "yourtable"
Dim ColOneParam = UpdateCmd.Parameters.Add("#Col1", OleDbType.Integer)
ColOneParam.SourceColumn = "Name of column in DataTable which corresponds to col1"
'Create a parameter to pass in the value of the "col2" column on "yourtable"
Dim ColTwoParam = UpdateCmd.Parameters.Add("#Col2", OleDbType.Integer)
ColTwoParam.SourceColumn = "Name of column in DataTable which corresponds to col2"
'Data adapter which will perform the specified update command for each
'newly inserted row
Using Adapter As New OleDbDataAdapter
'Set the update command on the adapter, if you omit this line you'll
'encounter the original error you mentioned
Adapter.UpdateCommand = UpdateCmd
'This is the data table containing the rows you wish to update
Dim NewRows As New DataTable("SomeTable")
'For each modified row in NewRows, update it in the database
Adapter.Update(NewRows)
End Using
End Using
End Using
The above code assumes a table in the database called yourtable with two columns col1 and col2 which are numeric.
The Using blocks simply ensure that the database objects are properly disposed off so you don't end up leaking resources.
Related
I am trying to update my SQL database with the code provided, I am basically trying to take a select statement to bring back just the INTGenderID based on a combo box and make it equal to a string then convert that to an integer when I do my update back into my database. If you need more info let me know. The main table is TGolfers and the parent table is TGenders.
I think I understand the question. Let's assume that you have a database table named Thing and that has columns ThingId and Name. You would generally query the database to populate a DataTable and then bind that to your ComboBox, e.g.
Dim table As New DataTable
Using connection As New OleDbConnection(connectionString),
command As New OleDbCommand("SELECT ThingId, Name FROM Thing", connection)
connection.Open()
Using reader = command.ExecuteReader()
table.Load(reader)
End Using
End Using
With thingComboBox
.DisplayMember = "Name"
.ValueMember = "ThingId"
.DataSource = table
End With
The user will now see a list of Name values in the ComboBox and, when they select one, you can get the corresponding ThingId from the SelectedValue property, e.g.
Dim thingId = CInt(thingComboBox.SelectedValue)
I am developing a simple savings account application where I am able to simulate deposit, withdrawal and transfer of money. I have a button called "Deposit" that I can click and data will be recorded in my database.
To make that insert statement I am using SQL parameters and this is the code:
SqlCon = New SqlConnection
SqlCon.ConnectionString = "............"
Try
Query = "INSERT INTO Transacoes(tpAccount, dateTransaction, descrTransaction, amoutTransaction, balanceTransaction)
VALUES(#tpAccount, #dateTransaction, #ddescrTransaction, #amoutTransaction, #balanceTransaction)"
SqlCon.Open()
SqlCmd = New SqlCommand(Query, SqlCon)
With SqlCmd.Parameters
.Add("#tpAccount", SqlDbType.Char).Value = cbTipoConta.Text
.Add("#dateTransaction", SqlDbType.DateTime).Value = txtDate.Text
.Add("#descrTransaction", SqlDbType.Char).Value = cmdDepositar.Text
.Add("#amoutTransaction", SqlDbType.Int).Value = txtDeposito.Text
.Add("#balanceTransaction", SqlDbType.Int).Value = txtBalance.Text
End With
SqlCmd.ExecuteNonQuery()
SqlCon.Close()
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
In this table I have an "ID" column but I don't want it to display on SQL parameters and shows me an error:
Cannot insert the value NULL into column 'Id'`.
I should autoincrement the Id and then this will work fine? Or what is the best way to solve this?
You should autoincrement the ID, I always use ID column with Identity(1,1) even though it is not needed at the moment. It is always nice to select specific row fast and when the ID is autoincremented, then you have only one where condition.
In order to be able to omit the ID field in your insert statement you need to set an auto increment (identity) on that field.
REF: https://technet.microsoft.com/en-gb/library/ms188263(v=sql.105).aspx
Identity is not the only solution in this case. The other one is named sequence.
In your case you can simply create a new sequence like in this example
create sequence dbo.id_sequence as int
start with 0
increment by 1;
More information about create of sequence you can find here
If you have a sequence you can use it as a defalut value for any column in table what you can see in example on point D here. You can even use more sequences to work with more than one column in a table if you want.
Difference between identinty and sequence is that you can use an identity only for one column (with sequence or even sequences you can do it with many columns in one table) and for set identity you need to recreate table (the alter is not allowed in this case) or remove column with identity and create a new one with it (and you need to be aware about constraints and relations with deleted column). With sequences you can simply alter the table in any moment with any column you need.
With sequences you can update or insert a value to a column (with identity you can't) so if you use this part of your work to simulate something it colud be a good idea to make a possibility of using original ids (if you need it of course) instead of that created by identity.
I am inserting data from my vb.net application to msaccess db.
I am confused in way of getting the last inserted record added to a table. IN MS-SQL we get ##IDENTITY for that but it didn't worked for me in MSAccess.
so what should be do for getting the last inserted record added to a table?
Example:
Dim db As Database
Set db = CurrentDb
db.Execute "INSERT INTO Table1 (atext) Values('abc')", dbFailOnError
Dim rs As dao.Recordset
Set rs = db.OpenRecordset("select ##identity")
Debug.Print rs(0)
It does require that there is an autoincrement key on the table.
Here is a short example using OleDb. Notice that I create a command from the connection and then reuse that object to make my select identity call. This ensures we are in the same scope and get the identity of the record we just inserted. This has the same effect of chaining the commands together with ";", like you would want to do in other DB SQL calls to return the identity with the insert command. ExecuteScalarAsync returns the response object which we can cast to our ID type.
Dim Identity As Integer
Dim recordsAffected As Integer
Using connection As New OleDbConnection(ConnectionString)
Await connection.OpenAsync()
Using command = connection.CreateCommand()
command.CommandText = "INSERT INTO table (field) VALUES (?)"
recordsAffected = Await command.ExecuteNonQueryAsync()
' Get the ID of last inserted record
command.CommandText = "SELECT ##IDENTITY"
Identity = CInt(Await command.ExecuteScalarAsync())
End Using
connection.Close()
End Using
It's always a good practice to have a numeric (even auto-increment) primary key. Then you can always select the MAX and that's the latest inserted record.
It's more complicated in Access than SQL Server because access doesn't support the execution of multiple statements in a batch or output parameters.
According to the MSDN documentation, you need to add a handler for the RowUpdated event.
Before resorting to this, however, I would try wrapping your insert code in a transaction and then executing the select ##identity method within the transaction. Might not work, but worth a shot.
As far as I know, MS Access does not have the functionality to get the last added row.
In practice, I create an autoincrement column (which is usually the Primary Key anyway). Then I run this query when I desire to get the last row in the table:
SELECT TOP 1 * FROM [Table] ORDER BY [IdColumn] DESC
It simply sorts the the rows in the table by the ID column in reverse order and takes the first one (which is really the last row in the table).
I am hitting an MS Access db with VB2005 and I am creating a table object like this
Dim sSQL As String = "SELECT * FROM tblCars WHERE WeekOf=#6/1/2011#"
Dim da As New OleDb.OleDbDataAdapter(sSQL, conn)
Dim ds As New DataSet
da.Fill(ds, "CarData")
Dim cb As New OleDb.OleDbCommandBuilder(da)
The table should have no rows since the data is brand new. I read a data text file with new data and pump this data into the table and add new rows like so:
'create a new empty row
Dim dsNewRow As DataRow = ds.Tables("CarData").NewRow()
'update the new row with fresh data
dsNewRow.Item("DriveDate") = dd
dsNewRow.Item("DCode") = dc
dsNewRow.Item("CarNum") = carID
'about 15 more fields
'add the filled row to the DataSet table
ds.Tables("CarData").Rows.Add(dsNewRow)
'end loop
'update the database with the new rows
da.Update(ds, "CarData")
The table in the db has no primary keys but all this works and i get all my new data inserted into the Data table tblCars.
Now it so happens that I must also account for cars with no data. For that my plan was to get a list of cars from the control table and merge it with the Data table.
Dim sSQL As String = "SELECT CarId FROM tblControlCars WHERE ActiveCar=True;"
Dim daAct As New OleDb.OleDbDataAdapter(sSQL, conn)
Dim ds As New DataSet
daTail.Fill(ds, "ActCars")
Dim cbAct As New OleDb.OleDbCommandBuilder(daAct)
I then create my primary keys for the tables and then merge them.
ds.Tables("CarData").Merge(ds.Tables("ActCars"))
This will give me a table with all the new data for cars that do have data and also a place holder for those cars that are active but did not get any data. However, when I try to do an update back to the db I get an error
System.InvalidOperationException: Dynamic SQL generation for the UpdateCommand is not supported against a SelectCommand that does not return any key column information.
I'm not sure I understand why the Merged data is not able to be pushed to the database.
I've had the same problem before just add a primary key in your database table , the command builder needs a unique identification field to generate a proper command .
You can simply create a field called "id" and make auto-number then you don't have to update it manually and make it a primary key that's it
i have a sql database, and have created a dataset for a sample table in it.
The dataset is HotelDataSet2 and data table adapter to insert to it is Various_itemsTableAdapter.
MsgBox(HotelDataSet2.various_items.Rows.Count)
Various_itemsTableAdapter.Insert(Item_nameTextBox.Text, itemcode, PackingTextBox.Text, UomTextBox.Text, PriceTextBox.Text, RemarksTextBox.Text, TaxTextBox.Text, StatusCheckBox.Checked, Rate_inclusiveCheckBox.Checked)
MsgBox(HotelDataSet2.various_items.Rows.Count)
This always reflects the same count, before and after inserting..But if i execute this
MsgBox(HotelDataSet2.various_items.Rows.Count)
Various_itemsTableAdapter.Insert(Item_nameTextBox.Text, itemcode, PackingTextBox.Text, UomTextBox.Text, PriceTextBox.Text, RemarksTextBox.Text, TaxTextBox.Text, StatusCheckBox.Checked, Rate_inclusiveCheckBox.Checked)
Various_itemsTableAdapter.Fill(HotelDataSet2.various_items)
MsgBox(HotelDataSet2.various_items.Rows.Count)
it shows the new count to be +1 of the old one. So i concluded that everytime i change some data to the table via table adapter, i have to always refill the dataset?? How is that useful then??
You're going about this the wrong way. You should be using the DataSet to make your changes (ie, create DataRow's with its tables and insert those rows into the table), then pass the appropriate DataTable from the DataSet to your TableAdapter in order to persist the changes to the database by calling the Update function on the TableAdapter
tableAdapter.Update(dataSetName);
Calling the Insert function directly only inserts that data into the table in the database itself; it does not have any effect on any local copies of the data (such as your DataSet).
As a side note, try to avoid using language-specific functions like MsgBox. Instead, to show a message box try calling MessageBox.Show(...), as that is language-independent.
Edit
To clarify, the proper way to do this is to create a new DataRow, populate its columns with the new values, add that DataRow to the appropriate table, then pass the table to the TableAdapter through the Update function.
For example, I have...
A DataSet named myDataSet
A DataTable in myDataSet called MyTable
A TableAdapter for MyTable called myTableAdapter
Two columns in that table, FirstName and LastName
Two TextBoxes called txtFirstName and txtLastName
To insert a row with those values, I would do this:
DataRow row = myDataSet.MyTable.NewRow(); // creates a new row with the correct columns
row["FirstName"] = txtFirstName.Text;
row["LastName"] = txtLastName.Text;
myDataSet.MyTable.Rows.Add(row); // adds the new row to the in-memory table
myTableAdapter.Update(myDataSet); // persists all of the changes to the DataSet