Merge Semantics Contract
The Merge Semantics Contract defines predictable, testable merge behavior across UI and non-UI flows and applies to:
Unchanged state.The merge API is expressed as four explicit dataset extension methods:
DoPostSaveMergeDoRefreshMergeIfNoChangesExistDoRefreshMergePreservingLocalChangesDoReplaceMerge
MergeWith exists for backward compatibility only and is marked [Obsolete].
New code should call the explicit Do* methods.
ExcludeTablesFromMerge - skip merging that table entirely.ExcludeTablesFromRowDeletion - never delete rows from that table due to "missing in refreshed".Current contract: tables without primary keys use "Replace-like" semantics during refresh operations because safe row-by-row reconciliation is not reliable without keys. This avoids false matches and accidental data corruption.
Unchanged.POCO DataSet and Observable POCO DataSet use the same policies. The only difference is that Observable POCO DataSet raises notification events about changes during the merge process.
Intent: refresh current content with server truth without overwriting local edits.
Unchanged state may be overwritten by refreshed values.Added, Modified, and Deleted) are preserved.Unchanged.Unchanged (and the rows are not excluded from deletion).| Current row state | Refreshed row exists | Action | Result | Observable Events |
|---|---|---|---|---|
Unchanged |
Yes | Copy refreshed values | Unchanged |
IObservableDataRow.DataFieldValueChanged |
Unchanged |
No | Remove row (unless rows excluded from deletion) | Row removed | IObservableDataTable.RowsRemoved |
Modified |
Yes/No | Preserve local edits | Modified |
None |
Added |
Yes/No | Preserve local new row | Added |
None |
Deleted |
Yes/No | Preserve pending delete | Deleted |
None |
Outcome: local edits remain as is; server-applied updates become baseline (Unchanged) for eligible rows.
Intent: enforce that refresh can only happen when the current dataset is a clean baseline. This mode is appropriate for read-only screens, polling, and search result scenarios where local edits are not expected.
Added, Modified, or Deleted state, an exception is thrown and no merge occurs.Unchanged.| Precondition | Current state | Refreshed snapshot | Action | Result | Observable Events |
|---|---|---|---|---|---|
| Must be clean | Any row is Added/Modified/Deleted |
Any | Throw and do not merge | No changes applied | None |
| Clean baseline | All rows are Unchanged |
Row exists with same PK | Copy refreshed values | Row stays Unchanged |
None |
| Clean baseline | All rows are Unchanged |
Row missing for a current PK | Remove row (unless excluded from deletion) | Row removed | None |
| Clean baseline | All rows are Unchanged |
New row exists (PK not present in current) | Add row | Row becomes Unchanged |
None |
Intent: apply server-confirmed values (identity, rowversion, computed fields), finalize deletes, and end in a clean baseline.
AcceptChanges() to commit the clean baseline.| Current row state | Server row exists | Action | Result | Observable Events |
|---|---|---|---|---|
Added |
Yes | Propagate identity/rowversion/computed fields | Unchanged |
IObservableDataRow.DataFieldValueChangedIObservableDataRow.RowStateChanged |
Modified |
Yes | Propagate server-confirmed fields | Unchanged |
IObservableDataRow.DataFieldValueChangedIObservableDataRow.RowStateChanged |
Deleted |
Yes/No | Remove from table (finalize delete) | Row removed | IObservableDataTable.RowsRemoved |
Unchanged |
Yes | Copy refreshed values | Unchanged |
IObservableDataRow.DataFieldValueChanged |
Outcome: all remaining rows end as Unchanged.
Intent: replace the entire local result set (new search results, "load children from scratch", etc.). This mode intentionally does not attempt row-level reconciliation and therefore is not considered a merge in the strict sense.
Unchanged.| Current content | Refreshed snapshot | Action | Result | Observable Events |
|---|---|---|---|---|
| Any rows (including pending edits) | Any | Remove all current rows | Table becomes empty | IObservableDataTable.RowsRemoved |
| Empty after clear | All refreshed rows | Add all refreshed rows | All rows are Unchanged |
IObservableDataTable.RowsAdded |
Replace is easy to accidentally change later. The current policy is:
Table of Content POCO DataSet Concepts
Business Process Programming in .Net
© 2004–2026 Laskarzhevsky Software Inc.
Unless otherwise noted, the content of this website is licensed under the
Creative Commons Attribution 4.0 International License (CC BY 4.0).
Code examples are provided under the MIT License.
You are free to share and adapt the material provided that appropriate
credit is given and any modifications are clearly indicated.
The information provided on this website is for educational purposes only.
The author and publisher make no warranties regarding the completeness
or suitability of the information and are not responsible for any damages
resulting from its use.