I'm having an issue with writing back to my Access Database (.accdb) through using a DataAdapter.
Currently, I have a Fill method which fills a DataSet Table up with data. This piece of code is currently sitting within my Form_Load().
// TODO: This line of code loads data into the 'hCAliasDataSet.Topics' table. You can move, or remove it, as needed.
this.topicsTableAdapter.Fill(this.hCAliasDataSet.Topics);
Then I have an cmdAdd_Click() event, this is obviously to add a new row into the Topcis table that sits within the hCAliasDataSet.
// Create a new row, append it to Topics table.
DataRow DR;
DR = this.hCAliasDataSet.Tables["Topics"].NewRow();
this.hCAliasDataSet.Tables["Topics"].Rows.Add(DR);
Next, I've created some code to caputre the input of one of the column values.
// Capture user input
sTopicName = Interaction.InputBox("Please Enter Topic Name", "Topic Name", null, 100, 100);
// Set the Topic value for the new Row
DR["Topic"] = sTopicName;
My problem, I'm assuming is here, where I call the dataAdapter to update the Topics table. I manually check the database, and there aren't any new rows created?
// Commit the changes back to the hCAlias DataBase (hcalias.accdb).
this.topicsTableAdapter.Update(this.hCAliasDataSet.Topics);
Edit: I believe I'm needing to create an INSERT query, for my TableAdapter, would this be correct?
The adapter should generate the insert statement automatically for you behind the scenes.
Your code looks right, but it's possible that you have a constraint on one of your columns that makes it unable to save. (like a non-nullable column that you didn't specify any data for). But, you'd usually get an exception if there was a constraint that cancelled the insert.
You can try one of these alternatives, but they're basically the same thing:
this.topicsTableAdapter.Update(this.hCAliasDataSet);
this.topicsTableAdapter.Update(DR);
this.topicsTableAdapter.Insert(sTopicName); // add any other columns in here
You should call AcceptChanges in your dataset:
hCAliasDataSet.AcceptChanges();
And then commit to the database with your TableAdapter.
The Update() method just updates existing records, you'll need to use the TableAdapter Insert() method to add a new row. VC# will have created a default Insert() method for you, (which may be overloaded)... but there will be a method that will let you explicitly insert values....
For example...
this.topicsTableAdapter.Insert(int Column1, string Column2 etc etc)
This will create a new row in your database and populate it with the values you specify.
Related
I'm wanting to do a bulk copy of data from one database to another. It needs to be dynamic enough so that when the users of the source database create new fields, there are minimal changes at the destination end(my end!).
I've done this using the sqlbulkcopy function, using column mappings set up in a seperate table, so that if anything new is created all I need to do is create the new field and set up the mapping (no code or stored procedure changes):
foreach (var mapping in columnMapping)
{
var split = mapping.Split(new[] { ',' });
sbc.ColumnMappings.Add(split.First(), split.Last());
}
try
{
sbc.WriteToServer(sourcedatatable);
}
However, the requirements have now changed.
I need to keep more data, sourced from elsewhere, in other columns in this table which means I can't truncate the whole table and write everything with the sqlbulkcopy. Now, I need to be able to Insert new records or Update the relevant fields for current records, but still be dynamic enough that I won't need code changes if the users create new fields.
Does anyone have any ideas?
Comment on original question from mdisibio - it looks like the SQL MERGE statement would have been the answer.
I have a c#.net application in which I need to insert the default value from application to sql server by using sql bulkcopy.
Example:
SqlColumnMapping("src_col1","dest_col1");
SqlColumnMapping("src_col2","dest_col2");
in "dest_col3", I would like to insert default value.
How could I map it in app and how the default value can be inserted in database?
Thanks
Hint: do not use SqlBulkCopy - that thing has tons of problems. Most around locking, default values also are in the game.
Use it against a temporary table ;)
THis is what I do.
Create a temp table with the proper field structure. You can make fields nullable here if they have a default value (information_schema can help you find it). THis step can be automated - 100% and it is not that hard.
SqlBulkCopy into the temp table. No locking issues.
After that you can run updates for default values ;)
INSERT INTO the final table.
Problems with SqlBulkCopy locking:
Locks the table. Exclusively.
It does not wait. It tries to get a lock, immediately. If that fails it retries. If the table is busy, it never gets the lock as it never waits until it gets one - and every new request is end of the queue.
We got hit badly by that in a ETL scenario some years back.
On top, as you found out, you can not work with default values.
I actually have that stuff totally isolated now in a separate bulk loader class and am just in the process of allowing this to UPDATE rows (by merging from the temp table).
Here's how you do it. Create a DataTable object that has the same structure as your desitination table, except remove the columns that have a default value. If you are using DataSet Designer in Visual Studio, you can remove the columns that have default values from the TableAdapter.
Using an SqlConnection called "connection" and a DataTable object called "table", your code would look something like:
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
{
foreach (System.Data.DataColumn c in table.Columns)
{
bulkCopy.ColumnMappings.Add(c.ColumnName, c.ColumnName);
}
bulkCopy.DestinationTableName = table.TableName;
bulkCopy.WriteToServer(table);
}
Again, in order to use this method, you have to ensure that your DataTable object does not contain the columns that you would like to insert with default values.
i have a many-to-many relationship table in a typed DataSet.
For convenience on an update i'm deleting old relations before i'm adding the new(maybe the same as before).
Now i wonder if this way is failsafe or if i should ensure only to delete which are really deleted(for example with LINQ) and only add that one which are really new.
In SQL-Server is a unique constraint defined for the relation table, the two foreign keys are a composite primary key.
Is the order the DataAdapter updates the DataRows which RowState are <> Unchanged predictable or not?
In other words: is it possible that DataAdapter.Update(DataTable) will result in an exception when the key already exists?
This is the datamodel:
This is part of the code(LbSymptomCodes is an ASP.Net ListBox):
Dim daTrelRmaSymptomCode As New ERPModel.dsRMATableAdapters.trelRMA_SymptomCodeTableAdapter
For Each oldTrelRmaSymptomCodeRow As ERPModel.dsRMA.trelRMA_SymptomCodeRow In thisRMA.GettrelRMA_SymptomCodeRows
oldTrelRmaSymptomCodeRow.Delete()
Next
For Each item As ListItem In LbSymptomCodes.Items
If item.Selected Then
Dim newTrelRmaSymptomCodeRow As ERPModel.dsRMA.trelRMA_SymptomCodeRow = Services.dsRMA.trelRMA_SymptomCode.NewtrelRMA_SymptomCodeRow
newTrelRmaSymptomCodeRow.fiRMA = Services.IdRma
newTrelRmaSymptomCodeRow.fiSymptomCode = CInt(item.Value)
Services.dsRMA.trelRMA_SymptomCode.AddtrelRMA_SymptomCodeRow(newTrelRmaSymptomCodeRow)
End If
Next
daTrelRmaSymptomCode.Update(Services.dsRMA.trelRMA_SymptomCode)
Thank you in advance.
I think that the DataAdapter in ADO.NET is clever enough to perform the delete/inserts in the correct order.
However, if you really want to ensure that updates are done in the correct order you should do it manually by using the Select method to return an array of data rows for each particular row state. You could then call the Update method on the array of data rows
DataTable tbl = ds.Tables["YourTable"];
// Process any Deleted rows first
adapter.Update(tbl.Select(null, null, DataViewRowState.Deleted));
// Process any Updated/Modified rows
adapter.Update(tbl.Select(null, null, DataViewRowState.ModifiedCurrent));
// Process the Inserts last
adapter.Update(tbl.Select(null, null, DataViewRowState.Added));
Not sure about the DA but in theory DB transactions should be performed in the following order Deletes, Inserts, Updates.
looking at msdn the exact wording for the update method is
Blockquote
Attempts to save all changes in the DataTable to the database. (This includes removing any rows deleted from the table, adding rows inserted to the table, and updating any rows in the table that have changed.)
Blockquote
In regards to your solution of deleting items and possibly re-inserting the same items, typically speaking this should be avoided because it creates a load on the DB. In high volume applications you want to do everything you can to minimize calls to the DB as they are very expensive; computation time, from determining which row updates are spurious, is cheap.
I have read from many places that it is possible to fill a DataSet with multiple tables using a DataAdapter. It also does not say whether a single Update call can update all the tables in the DataSet.
Can someone help me figure out how this can be done?
It seems like there isn't any ( i tried finding online ) examples on how to do it except for one that changes the SelectCommand on the DataAdapter before the second fill. But I feel this method defeats the purpose of the DataAdapter.
From what I figure, perhaps a single DataAdapter can only handle a single database table and Update only works on that table. Hence a multi-table DataSet will require respective DataAdapters call their Update to fully update the DataSet. Is this the case?
Finally, will foreign key relations and contraints hold in a DataSet (cascade delete, cascade update) automatically?
Maybe a link to an example or tutorial might help. Many thanks!
Yes it is true that, Single Adapter for single table.But
You can use Use table adapter manager for saving all at once, table adapter manager can have many individual adapters and you can call save for all. like, So no need to call save multiple time also it has other features too.
public void SaveWithManager()
{
DataSet1TableAdapters.TableAdapterManager mgr1 = new DataSet1TableAdapters.TableAdapterManager();
DataSet1TableAdapters.Table1TableAdapter taTbl1 = new DataSet1TableAdapters.Table1TableAdapter();
DataSet1TableAdapters.Table2TableAdapter taTbl2 = new DataSet1TableAdapters.Table2TableAdapter();
mgr1.Table1TableAdapter = taTbl1;
mgr1.Table2TableAdapter = taTbl2;
mgr1.UpdateOrder = DataSet1TableAdapters.TableAdapterManager.UpdateOrderOption.InsertUpdateDelete;
mgr1.UpdateAll(your dataset);
}
Finally cascade update delete is handled in dataset. You can view properties of relation and various options for cascade.(Typed dataset)
Operations:
Delete in DataGridView selected row from Dataset:
FuDataSet.FuRow row = (FuDataSet.FuRow) ((DataRowView)FuBindingSource.Current).Row;
row.Delete();
To add a new Row I'm doing:
FuDataSet.FuRow row = FuDataSet.Fus.NewFuRow();
row.Someting = "Some initial Content";
row.SomethingElse = "More Initial Content";
...
FuDataSet.Fus.AddFuRow(row);
Saving user changes in current row in Dataset:
FuDataSet.FuRow row = (FuDataSet.FuRow) (((DataRowView) FuBindingSource.Current).Row);
row.Someting = someTextBox.text;
...
Save in Database:
Validate();
FuBindingSource.EndEdit();
FuTableAdapter.Update(FuDataSet.Fus); <-- Exception here
I'm using the standard DatagridView, Dataset, TableAdapter, BindingSource Scheme VS puts automaticly up after defining the database structure. There is only a single table involved and SQL Server compact 3.5 is used.
Now my problem is that I get a Concurrency Exception (DeletedRowInaccessibleException) each time I'm doing this (starting with an empty database):
Creating a new row, delete this row, save in Database, new row, save in database, delete this row, save in database <- Exception
I think that there is some synchroniszing problem between the database and the dataset.
If I'm reloading the databse after each save via FuTableAdapter.Fill(FuDataSet.Fus) the problem is gone. However, this cannot be the intention I think.
I hope someone can help me out and spot a failure in the design or explain me what may go wrong.
Thank you!
Does your table have an auto increment identity column as the primary key? If so it might not be updating the dataset table with the new value after the insert, so when you come to delete it, it cannot find the row in the database. That could explain why it works once you called the Fill() method.
You will need to somehow return the primary key on the insert so that the dataset table stays in sync with database. If you are using a store procedure to do the inserts, then primary key can be returned using an out parameter. Not sure what the best way is if you are using an SQL insert statement in the command, but you will then have to get the primary key back from the database table and assign it to the database table row.
Not sure if you are doing this after the saveing, but calling FuDataSet.AcceptChanges() will help the dataset track new changes after the database has been updated.
What you have listed there is correct. When a new row is created in the dataset table, it creates it's own ID. When you save to the database, the database table creates it's own ID as well, which in most cases will be different to the one in the dataset.
When you created the table adapter for that table, you had to supply a sql state to create the dataset table. On the advanced Options button, there is a checkbox called "Refresh the data table". Check that to have a sql statement added after the insert and update to retrieve the identity column.
If the checkbox is disabled then I am not sure what else you could, other than reload the data after each save, which will not be optimal.
Sorry I cannot be of more assistance. Best of luck