Update from DataGridView with SqlDataAdapter - sql-server

I have a DataGridView that displays data from a SQL Server database. It allows the user to edit the data and save it back to the database.
The way that data gets saved back to the database is something like this:
Dim da As New SqlDataAdapter
Dim cmdBuilder As New SqlCommandBuilder(da)
da.SelectCommand = New SqlCommand(sql, conn)
da.InsertCommand = cmdBuilder.GetInsertCommand
da.UpdateCommand = cmdBuilder.GetUpdateCommand
Dim cb As SqlCommandBuilder = New SqlCommandBuilder(da)
da.Update(dt)
This works fine when I'm saving to ordinary tables. However I also want to save to a view that has an INSTEAD OF trigger that fires on insert, update and delete. When I try to use the above with this view I get the error:
Dynamic SQL generation for the UpdateCommand is not supported against a SelectCommand that does not return any key column information.
How can I save to this view? I don't want to save directly to the underlying table because I want the trigger to fire.
Thanks!
EDIT: I also tried manually generating the InsertCommand and UpdateCommand, but got the same error.
EDIT 2: It turned out I got the commands wrong when I manually generated them. Once I fixed that it worked fine - my view gets updated and the trigger fires as expected. I guess that you just can't autogenerate the commands for a view with SqlCommandBuilder.

a quick google search shows that this problem may occur if your table does not have a primary key value. your select query must return that value for the update query (in a case where there is no primary key column, it wouldnt.)
http://social.msdn.microsoft.com/Forums/en-NZ/Vsexpressvcs/thread/5dec5633-ac84-48d9-8fd6-5c7601be4ccd

Related

In vb.net, why am I getting a duplicate key error when I have used a revised key?

I am using Visual Studio 2015 Community edition and programming in vb.net using ms sql-server 2015. I have set up the data adapter and data table in the form load event. (see below)
Private m_DABinders As New SqlDataAdapter
Private m_CBBinders As New SqlCommandBuilder
Private m_DataTableBinders As New Data.DataTable
I use the code below to insert a row into the Binders table and to check and handle the duplicate key problem. This works fine. My problem comes up when after I get this error, I change the key value to something unique and try to add the row again, and I still get the same duplicate key error, which, strangely enough, shows the original duplicate key value rather than the new one. (I have used MessageBox to ensure that the updated key value is being placed into the appropriate column.) It behaves as if it's still trying to insert the row with the duplicate key before it inserts the updated row.
Do I somehow have to remove the "bad" row from the data adapter or data table after the error? I notice by doing a row count that the rows on the data table goes up by one each time I get this error so I'm thinking I have to delete it somehow but I'm not sure how.
I'm very new to programming in vb.net and with ms sql-server so please don't assume much prior knowledge in your replies. Thanks.
Here is the sub-routine that I use to insert the row.
Public Sub CreateBinderRow()
Try
Dim keyValueBinder = tbLocation.Text & tbProject.Text & tbBinder.Text
Dim drNewBinderRow As DataRow = m_DataTableBinders.NewRow()
drNewBinderRow("KeyValue") = keyValueBinder
drNewBinderRow("Location") = tbLocation.Text
drNewBinderRow("ProjectName") = tbProject.Text
drNewBinderRow("BinderName") = tbBinder.Text
drNewBinderRow("LastUpdated") = Now
m_DataTableBinders.Rows.Add(drNewBinderRow)
m_DABinders.Update(m_DataTableBinders)
boolCreateBinderOK = True
Catch dbException As System.Data.SqlClient.SqlException
boolCreateBinderOK = False
MessageBox.Show("SQLException: " & dbException.ToString)
MessageBox.Show("Error creating Binder row - probably duplicate values", "Binder - Error Creating Binder Row",
MessageBoxButtons.OK, MessageBoxIcon.Error)
Catch dbException As Exception
boolCreateBinderOK = False
MessageBox.Show("SQLException: " & dbException.ToString)
MessageBox.Show("Error creating Binder row", "Binder - Error Creating Binder Row",
MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub
BTW - The error I am getting is: SqlException (0x80131904) Violation of Primary Key constraint 'PK.Binders' Cannot insert duplicate key in object 'dbo.Binders'. The duplicate key value is (my data). The statement has been terminated.
I think you need to commit the changes to the DataTable before updating the
DataSet.
m_DataTableBinders.Rows.Add(drNewBinderRow)
//add this line to commit the changes
m_DataTableBinders.AcceptChanges()
m_DABinders.Update(m_DataTableBinders)
Typically you would not hold a long-term reference to your DataTable.
A common code pattern using ADO.NET for database updates looks like:
Using connection As SqlConnection = New SqlConnection("connection string")
connection.Open
Using command As SqlCommand = New SqlCommand("INSERT INTO Binders (KeyValue, Location, ProjectName, BinderName, LastUpdated) VALUES (#KeyValue, #Location, #ProjectName, #BinderName, #LastUpdated)", connection)
command.Parameters.AddWithValue("#KeyValue", keyValueBinder)
' TODO: Add the rest of the command parameters '
command.ExecuteNonQuery
End Using
End Using

Updating SQL server table with vb.net 2010 datagridview

I am having trouble updating my database table with datagridview, I've read some thread about the problem and nothing seem to work in my case. Here is the code i use when populating the datagridview.
Public Sub filldatagridserver(ByRef SQL As String, ByRef dg As DataGridView)
Dim myDataset As DataSet = New DataSet
myAdptr.SelectCommand = New SqlCommand(SQL, cnSQL)
myAdptr.Fill(myDataset)
dg.DataSource = myDataset.Tables(0)
dg.AutoResizeColumns()
myAdptr.Dispose()
myDataset.Dispose()
cnSQL.Close()
End Sub
then i call this sub procedure with the ff code:
Call filldatagridserver("SELECT * FROM tblQuarterCollection", dgQuarter)
i already used myAdptr.update but i keep getting error about table mapping etc. I also tried to quote out the dispose and close in that sub procedure but it still doesn't work.
is there anyway to properly populate the datagridview and update changes made to database. I think my last resort here would be looping thru each record the update the data according to current value in the datagridview. Please help.
try this :
Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click
Dim addcmd as new sqlcommand("INSERT INTO TableName VALUES(#col1,#col2)",YourConnectionString)
addcmd.parameters.addwithvalue("#col1",txtCol1.text)
addcmd.parameters.addwithvalue("#col2",txtCol2.text)
addcmd.executenonQuery
End Sub
At last I managed to get my code working. I reviewed my database and reset the primary key and now it is working perfectly fine
If you call the Update passing also the name of the table then you need to add that name when you fill initially the DataSet
myAdptr.SelectCommand = New SqlCommand(SQL, cnSQL)
myAdptr.Fill(myDataset, "tblPayMTC")
Another important point to get the automatic update working is to have a primary key returned by the SELECT query otherwise you need to write your own commands for the UpdateCommand, InsertCommand and DeleteCommand of the SqlDataAdapter instance.
The primary key is a column in your table tblQuarterCollection that uniquely identifies your records (usually an ID column with Identity set to true) but it can be also a group of columns that taken togheter works as Primary Key.
While it is not mandatory to have a column with this property set, it is highly advisable to have one

SQL Server CE - Problems updating table

I'm sure my question has been answered many times on the internet, but I couldn't find exactly what I was looking for.
I'm working on VB.NET and my database is a SQL Server Compact .SDF file. The following is my method of opening the database.
Private Shared Sub OpenDatabase(ByVal tablename As String)
If _DBLoaded Then Return
'// open database connection
conn = New SqlCeConnection("Data Source = giadatabase.sdf")
conn.Open()
'// create command for making extracting data
cmd = conn.CreateCommand
cmd.CommandText = "SELECT * FROM [" & tablename & "]"
'// setup database adapter
da = New SqlCeDataAdapter(cmd)
'// create command for inserting/updating database
cb = New SqlCeCommandBuilder(da)
'// load dataset
ds = New DataSet()
da.Fill(ds)
'// get the relevant table
dt = ds.Tables(0)
_DBLoaded = True
End Sub
I run this sub when my application starts. I believe that database needs to be opened just once. Constantly reopening of database will cause performance problems to my application (correct me if I'm wrong).
For loading data in my list object I use the following:
Public Shared Function GetList() As List(Of DatabaseListObject)
OpenDatabase("TestTable")
'// Make a list of items in database
Dim ret As New List(Of DatabaseListObject)
For Each dRow As DataRow In dt.Rows
ret.Add(New DatabaseListObject(dRow("ID"), dRow("LongName"), dRow("ShortName")))
Next
Return ret
End Function
So my GetList function ensures database is already open, and database is always opened once a lifetime of my application. My list object is filled with data from the above function.
This is how I make changes to my database:
Public Shared Function AddItem(LongName As String, ShortName As String) As DatabaseListObject
'// Make changes
Dim row = dt.NewRow()
row("LongName") = TimeOfDay.ToString
row("ShortName") = ShortName
dt.Rows.Add(row)
da.Update(ds, dt.TableName)
Dim newcmd = conn.CreateCommand
newcmd.CommandText = "SELECT ##IDENTITY;"
Dim newID As Integer = newcmd.ExecuteScalar()
Dim item As New DatabaseListObject(newID, LongName, ShortName)
Return item
End Function
Now I assume database is correctly updated from the above code. The ID column in my table is the autonumber. Problem occurs when I call the GetList function after adding a row. System throws error that the newly added row's ID column is NULL. Whereas it should be automatically added number. When I restart the application, i.e. the database is opened from scratch, then the GetList shows the autonumber properly.
Obviously the table's ID column is not getting filled-in with the autonumber when I'm adding a new row. So I need suggestions here. Should I always open the database from scratch whenever I call the GetList (which will be called frequently in my app). If not the entire database then which codes should be called at least to properly refresh the table without causing much performance problems to the application.
SELECT ##IDENTITY will only Work on the same open Connection object, and the DataAdapter opens and Closes its own connection, you must use plain ADO.NET (cmd.ExecuteNonQuery) or implemet extra code as described here: http://blogs.msdn.com/b/bethmassi/archive/2009/09/15/inserting-master-detail-data-into-a-sql-server-compact-edition-database.aspx

How to update database from unchanged datatable

I want to update my server database from my local database
Dim mycon As New SqlConnection(myserverConString)
Dim myAdapter As New SqlDataAdapter("SELECT * FROM MYTABLE", mycon)
Dim mybuilder As New SqlCommandBuilder(myAdapter)
myAdapter.UpdateCommand = mybuilder.GetUpdateCommand(True)
dim dt as datatable
'code to fill dt from my local db
'using select statement
For Each DR As DataRow In dt.Rows
DR.SetModified()
Next
dt.AcceptChanges()
myAdapter.Update(dt)
the problem is the used datatable in fact contain no changes
and the command builder updatecommand use the wrong "where"
I found the used update command is
UPDATE [MYTABLE] SET [COLUMN0] = #COLUMN0, [COLUMN1]=#COLUMN1 WHERE
([COLUMN0]=#Original_COLUMN0) AND ([COLUMN1]=#Original_COLUMN1))
COLUNM0 IS THE PRIMARY KEY
COLUMN1 IS THE UPDATED VALUE IN THE LOCAL DB,
AND I WANT TO UPDATE IT IN THE SERVER DB WHICH IS THE SAME SCHEMA.
COLUMN1 CAN'T used in "WHERE clause " as it is the changed value
and the dt doesn't have an original value for it.
My code doesn't give any errors,
but it also doesn't update anything.
If you are updating from your local DB why check the value of COLUMN1 in the WHERE clause? Just update all rows to the local COLUMN1 value.
Just use
UPDATE [MYTABLE]
SET [COLUMN1] = #COLUMN1
WHERE [COLUMN0] = #Original_COLUMN0
EDIT: You may well be better off adding a timestamp to your rows to make it easier for the SqlCommandBuilder to detect changes.
From The SQLCommandBuilder is way Better than its Reputation
The SQLCommandBuilder supports different ConflictOptions to detect
optimistic update conflicts:
OverwriteChanges (= no control) CompareAllSearchableValues (compares
the values of all searchable columns) CompareRowVersion (compares the
value the column with datatype RowVersion (~Timestamp) I prefer using
CompareRowVersion. See DB Concurrency Control with .NET-Details.
Also have a read of DB Concurrency Control with .NET – Details to see how to do this.
I found the answer,
just added one line of code before update statement
dt=dt.getchanges()
myAdapter.update(dt)

How to get the auto increment primary key in old mdb database?

I have to retrieve the Auto_Increment primary key (id) after inserting a new row in a mdb access database.
Having mdb file in the old Access 97 version too, I cannot use "SELECT ##Identity;", because it is not supported.
Besides, in a multi users environment I do not like the idea to use Max(ID).
Actually the only solution I have, it is to use the DAO as:
Dim db As Database 'Test Database
Dim rs As Recordset 'Test Table
...
rs.AddNew
id = rs!id
but I have to add a reference to the DAO COM DLL, while I would like to have a full managed .NET code.
Do you have any suggestion how to retrieve the Auto_Increment primary key (after inserting a new row) without using DAO or Max(ID)?
Here my code to add a new row:
Using oConn As New OleDbConnection(m_ConnString)
oConn.Open()
Using cmd As New OleDbCommand(sqlInsert, oConn)
cmd.ExecuteNonQuery()
End Using
End Using
Thank you.
If there is any other mandatory field in this table that has a unique index, you may query its value after your insertion with something like DFirst("ID", "tablename", "myfield = givenvalue"). If you're running in a transaction, use a recordset (perhaps the same as for the insertion) instead of DFirst.

Resources