I haven't used Access for quite a long time. I'm used to using SQL Server and there's an important difference in their ADO.NET providers that I'd forgotten about. Here's a summary of what I know about master/detail relationships with Access, SQL Server and ADO.NET:

The SqlClient ADO.NET provider for SQL Server supports multiple SQL statements in the one SqlCommand. That means that if you have a command that contains an INSERT statement, you can include a SELECT statement immediately after it to retrieve any values generated by the database, including identity and default values. If you insert by calling ExecuteNonQuery you would retrieve those values using output parameters. If you insert using a DataAdapter you would retrieve the entire record and the entire DataRow will be refreshed.

When you generate a DataTable schema from a SQL Server table, the AutoIncrementSeed and AutoIncrementStep properties of the PK column are set to 0 and 1 respectively, so as to closely resemble those values used by the SQL Server identity column. When you add new rows to your DataTable it will generate temporary values in the PK column. When you save those new rows to the database, the PK values will be updated from the database as described above. When inserting parent records, if the DataRelation is configured to cascade updates, the new PK value is propagated to any new child records before they too are inserted.

The Jet (MDB) and ACE (ACCDB) OLEDB providers do not support multiple SQL statements in the same OleDbCommand, so this means that can NOT retrieve AutoNumber values that get generated when you insert new records. If the data is unrelated you can just re-get all the data but this is no good for related data because you can end up with orphan records in the DataSet.

When you generate a DataTable schema from an Access table the AutoIncrementSeed and AutoIncrementStep properties of the PK column are both set to -1. This ensures that, when you add new records, the PK values generated by the DataTable will never clash with the real values generated by the database. When you save new records to the database the PK values in the DataSet are not refreshed, so the PK values in the DataSet don't match the PK values in the database. This doesn't really matter though, because data integrity is still maintained. The ONLY limitation that I can think of is that you cannot add a new row and save it, then delete it straight away. That's because the ID in the DataSet doesn't match the ID in the database, so the DELETE statement wouldn't affect any records. You'd have to get the data anew from the database before you could delete those new records.

Now, what does this mean for you in this project? Here's what I would suggest. In the database, you should configure each foreign key to cascade deletes. It doesn't matter whether updates are cascaded or not because the PKs will never change once generated. In the DataSet you should set the DataRelations to cascade deletes also. Again, whether or not updates are cascaded doesn't matter because the PKs will never change once generated. You should also set the AcceptRejectRule to Cascade. When it comes time to save data, you simply save all the grandparent data with a single Update call, then do the same for the parent data and, finally, the same for the child data. This should all just work as all the changes that need to be cascaded will be cascaded. If there's any chance that you'll need to delete records that you have just inserted then you should discard all the data and get it all again from the database.

As far as I can see, this should all just work, with a total of three lines of code to save the lot.