Philip,
First off, I'd recommend avoiding using DataTable.GetChanges for submitting the pending changes, if possible. GetChanges creates a new DataTable with copies of the modified rows from the original DataTable. Submitting updates in this fashion does not mark the modified rows in the original DataTable as unchanged at the end of the call to Update. Using this approach can also cause problems when working with auto-increment columns. GetChanges is more helpful in scenarios involving WebServices, or other components that require passing the contents of the DataSet into another process.
A better option is to use the overloaded Select method on the DataTable, which will return an array of DataRows, without making a separate copy of the DataRows. The Select method accepts parameters for a filter (such as "WHERE Country = 'Canada'"), a sort order (such as "City DESC") and row states. You can pass empty strings for the first two parameters if you are not interested in using a filter or sort order. You can use this method as shown here to return just the deleted rows:
DataTable.Select("", "", DataViewRowState.Deleted)
The Update method on the DataAdapter and TableAdapter classes is overloaded to handle an array of DataRows, so your code would look like:
//Delete from child tableadapter_child.Update(myChildTable.Select("", "", DataViewRowState.Deleted);
It sounds like you have the logic (top-down for inserts and updates, bottom-up for deletes) correct. I didn't see a reason for the DBConcurrencyException you described.
Now, getting back to the auto-increment issue. If you're working with a SQL Server database, the TableAdapter Configuration Wizard should have auto-configured the TableAdapter to retrieve the new auto-increment value after submitting the change to the database. If you're working with similar functionality for another database (Jet or MySQL auto-increment columns, Oracle sequences, etc.), you'll need to supply your own logic to retrieve this information.
By using DataTable.Select rather than DataTable.GetChanges, the newly retrieved auto-increment values will be stored in your original DataSet rather than a seprate copy that contains only the changed rows. Once you've retrieved these values into your DataSet, the goal is to have those new values cascade down to the related child rows.
The strongly typed DataSet automatically creates DataRelations to help you move from parent rows to child rows (or from child rows to parent rows). However, the wizards that create these DataRelations in Visual Studio .NET 2005 do not create ForeignKeyConstraints for the DataRelations. It's really the ForeignKeyConstraint that handles cascading changes to related rows.
To create a ForeignKeyConstraint for your DataRelation, select the DataRelation in the strongly typed DataSet designer and edit the DataRelation (by double-clicking on the DataRelation, or using the Visual Studio .NET menu). On the dialog that appears, select "Both Relation and Foreign Key Constraint" and set the Update and Delete Rules to Cascade.
Now when your TableAdapter retrieves new auto-increment values after submitting pending inserted parent rows, those new values will automatically cascade down to the related child rows.
I'd also strongly recommend having ADO.NET generate negative placeholder values for auto-increment columns for pending inserts (-1, -2, -3, ...). Select the auto-increment column in the strongly typed DataSet designer. Then, in the Properties window, set the AutoIncrementSeed and AutoIncrementStep properties to -1. This approach ensures that the placeholder values that ADO.NET generates will not conflict with values that already exist in the database. Another benefit to this approach is that it avoids the possibility of violating constraints within the DataSet.
I hope this information proves helpful.
David Sceppa / ADO.NET Program Manager / Microsoft |