Search code examples
c#.netwinformsdata-bindingado.net

C# WinForms multiple child grids in master-details data binding


Here's what I'm trying to do:

  • VS 2022, .NET Framework 4.8
  • Windows Forms application
  • All data handling is DataSets, DataTables, etc., All designer generated.

On a form, I have a DataGridView (the Master), bound to a BindingSource, bound to a single DataTable inside a DataSet.

There is also a "details" section with individual edit controls (TextBox, DateTimePicker, etc) bound to fields in the current master record. This all works fine.

There are two "child collections" associated with each "master" row. These are rows in additional tables joined to the parent with "Both Relation and Foreign Key Constraint" in the dataset designer.

Each child collection has a DataGridView, bound to a BindingSource, bound to the foreign key relation in the master binding source.

So, the overall form layout is Parent-Grid, Parent-detail, Child1-Grid, Clild2-Grid.

This all works great when viewing/editing records, but when trying to create new records, it's annoyingly close to working, but not quite.

For example, I add a new Master row (by clicking Add on its BindingNavigator). Row appears. I add a row to the first child collection (by clicking Add on its BindingNavigator). Row appears. I add a row to the second child collection. Row appears.

I now edit one of the master fields in the details section and POOF! My new rows in the child collections disappear. Or I click Save to save all the changes and the new child rows disappear. Or I add multiple rows and when I save the last one disappears. By the way - all of this disappearing happens before the DataSet is saved to the database - this is all interaction between DataGridView, BindingSource and so on, all through designer generated code.

Am I just asking too much of Data Binding in Windows Forms, or is there something fundamental that I'm missing when editing parent-child collections?


Solution

  • Here's what I ended up doing.

    • Set all BindingSource data sources to one dataset that serves the entire form. I did not bind anything to the foreign key relations exposed by the "parent" table.
    • Set the DataMember of each binding source to the corresponding table.
    • Use the Filter expression each child binding source to select only those rows that correspond to the current member of the "parent" table. Update that Filter expression in the event handler for ParentBindingSource.CurrentChanged, making sure to set it to an expression that would select nothing if there is no current parent.
    • Stop using BindingNavigator and just use ordinary ToolStrips and buttons. I only needed New and Delete, so mostly the BindingNavigator was extra weight anyway.
    • Add new rows by directly calling the AddXxxxxRow methods on the designer generated DataTable classes, specifying values for all rows so invalid row contents never occur.
    • Add interlocks around adding new rows to the tables to prevent any action in the BindingSource.CurrentChanged event handlers from running while an insert was underway. This seems to be the source of a great deal of strange behavior as this and other events are raised while the table is being updated, so bound controls see transient invalid states that cause all sorts of problems, including internal consistency failures from DataTable itself.
    • Delete rows by calling BindingSource.RemoveCurrent().
    • Hook both the XxxxxDataTable.XxxxxRowChanged (designer generated) and BindingSource.ListChanged events to detect when edits have been made to enable the Save button in the ToolStrip.
    • Some of my controls include combo boxes that are data bound to other tables in the dataset. These tables can be reloaded while the form is in use. I had to unbind these controls from their DataRowView, update the tables, then re-bind the controls. Without that step the control would end up changing all the combo boxes to their respective first items, resulting in spurious edits to the "child" row under construction.

    So, in the end, I actually kept most of the databinding machinery that the designer produced - I "just" stopped using binding to foreign key relations and handled all of the child table filtering myself.

    All foreign key update propagation is handled by the designer generated DataSet, so deleting a parent transparently deletes all the linked child rows as well, just as you'd expect.