Merging DataSet Contents
You can use the Merge method to merge the contents of a DataSet, DataTable, or DataRow array into an existing DataSet
. Several factors and options affect how new data is merged into an existing DataSet
.
Primary Keys
If the table receiving new data and schema from a merge has a primary key, new rows from the incoming data are matched with existing rows that have the same Original primary key values as those in the incoming data. If the columns from the incoming schema match those of the existing schema, the data in the existing rows is modified. Columns that do not match the existing schema are either ignored or added based on the MissingSchemaAction parameter. New rows with primary key values that do not match any existing rows are appended to the existing table.
If incoming or existing rows have a row state of Added, their primary key values are matched using the Current primary key value of the Added
row because no Original
row version exists.
If an incoming table and an existing table contain a column with the same name but different data types, an exception is thrown and the MergeFailed event of the DataSet
is raised. If an incoming table and an existing table both have defined keys, but the primary keys are for different columns, an exception is thrown and the MergeFailed
event of the DataSet
is raised.
If the table receiving new data from a merge does not have a primary key, new rows from the incoming data cannot be matched to existing rows in the table and are instead appended to the existing table.
Table Names and Namespaces
DataTable objects can optionally be assigned a Namespace property value. When Namespace values are assigned, a DataSet can contain multiple DataTable objects with the same TableName value. During merge operations, both TableName and Namespace are used to identify the target of a merge. If no Namespace has been assigned, only the TableName is used to identify the target of a merge.
Note
This behavior changed in version 2.0 of the .NET Framework. In version 1.1, namespaces were supported but were ignored during merge operations. For this reason, a DataSet that uses Namespace property values will have different behaviors depending on which version of the .NET Framework you are running. For example, suppose you have two DataSets
containing DataTables
with the same TableName property values but different Namespace property values. In version 1.1 of the .NET Framework, the different Namespace names will be ignored when merging the two DataSet objects. However, starting with version 2.0, merging causes two new DataTables
to be created in the target DataSet. The original DataTables
will be unaffected by the merge.
PreserveChanges
When you pass a DataSet
, DataTable
, or DataRow
array to the Merge
method, you can include optional parameters that specify whether or not to preserve changes in the existing DataSet
, and how to handle new schema elements found in the incoming data. The first of these parameters after the incoming data is a Boolean flag, PreserveChanges, which specifies whether or not to preserve the changes in the existing DataSet
. If the PreserveChanges
flag is set to true
, incoming values do not overwrite existing values in the Current
row version of the existing row. If the PreserveChanges
flag is set to false
, incoming values do overwrite the existing values in the Current
row version of the existing row. If the PreserveChanges
flag is not specified, it is set to false
by default. For more information about row versions, see Row States and Row Versions.
When PreserveChanges
is true
, the data from the existing row is maintained in the Current row version of the existing row, while the data from the Original row version of the existing row is overwritten with the data from the Original
row version of the incoming row. The RowState of the existing row is set to Modified. The following exceptions apply:
If the existing row has a
RowState
ofDeleted
, thisRowState
remainsDeleted
and is not set toModified
. In this case, the data from the incoming row will still be stored in theOriginal
row version of the existing row, overwriting theOriginal
row version of the existing row (unless the incoming row has aRowState
ofAdded
).If the incoming row has a
RowState
ofAdded
, the data from theOriginal
row version of the existing row will not be overwritten with data from the incoming row, because the incoming row does not have anOriginal
row version.
When PreserveChanges
is false
, both the Current
and Original
row versions in the existing row are overwritten with the data from the incoming row, and the RowState
of the existing row is set to the RowState
of the incoming row. The following exceptions apply:
If the incoming row has a
RowState
ofUnchanged
and the existing row has aRowState
ofModified
,Deleted
, orAdded
, theRowState
of the existing row is set toModified
.If the incoming row has a
RowState
ofAdded
, and the existing row has aRowState
ofUnchanged
,Modified
, orDeleted
, theRowState
of the existing row is set toModified
. Also, the data from theOriginal
row version of the existing row is not overwritten with data from the incoming row, because the incoming row does not have anOriginal
row version.
MissingSchemaAction
You can use the optional MissingSchemaAction parameter of the Merge
method to specify how Merge
will handle schema elements in the incoming data that are not part of the existing DataSet
.
The following table describes the options for MissingSchemaAction
.
MissingSchemaAction option | Description |
---|---|
Add | Add the new schema information to the DataSet and populate the new columns with the incoming values. This is the default. |
AddWithKey | Add the new schema and primary key information to the DataSet and populate the new columns with the incoming values. |
Error | Throw an exception if mismatched schema information is encountered. |
Ignore | Ignore the new schema information. |
Constraints
With the Merge
method, constraints are not checked until all new data has been added to the existing DataSet
. Once the data has been added, constraints are enforced on the current values in the DataSet
. You must ensure that your code handles any exceptions that might be thrown due to constraint violations.
Consider a case where an existing row in a DataSet
is an Unchanged
row with a primary key value of 1. During a merge operation with a Modified
incoming row with an Original
primary key value of 2 and a Current
primary key value of 1, the existing row and the incoming row are not considered matching because the Original
primary key values differ. However, when the merge is completed and constraints are checked, an exception will be thrown because the Current
primary key values violate the unique constraint for the primary key column.
Note
When rows are inserted into a database table containing an auto incrementing column such as an identity column, the identity column value returned by the insert may not match the value in the DataSet
, causing the returned rows to be appended instead of merged. For more information, see Retrieving Identity or Autonumber Values.
The following code example merges two DataSet
objects with different schemas into one DataSet
with the combined schemas of the two incoming DataSet
objects.
using (SqlConnection connection =
new(connectionString))
{
SqlDataAdapter adapter =
new(
"SELECT CustomerID, CompanyName FROM dbo.Customers",
connection);
connection.Open();
DataSet customers = new();
adapter.FillSchema(customers, SchemaType.Source, "Customers");
adapter.Fill(customers, "Customers");
DataSet orders = new();
orders.ReadXml("Orders.xml", XmlReadMode.ReadSchema);
orders.AcceptChanges();
customers.Merge(orders, true, MissingSchemaAction.AddWithKey);
}
Using connection As SqlConnection = New SqlConnection(
connectionString)
Dim adapter As New SqlDataAdapter(
"SELECT CustomerID, CompanyName FROM Customers", connection)
connection.Open()
Dim customers As New DataSet()
adapter.FillSchema(customers, SchemaType.Source, "Customers")
adapter.Fill(customers, "Customers")
Dim orders As New DataSet()
orders.ReadXml("Orders.xml", XmlReadMode.ReadSchema)
orders.AcceptChanges()
customers.Merge(orders, True, MissingSchemaAction.AddWithKey)
End Using
The following code example takes an existing DataSet
with updates and passes those updates to a DataAdapter
to be processed at the data source. The results are then merged into the original DataSet
. After rejecting changes that resulted in an error, the merged changes are committed with AcceptChanges
.
DataTable customers = dataSet.Tables["Customers"]!;
// Make modifications to the Customers table.
// Get changes to the DataSet.
DataSet dataSetChanges = dataSet.GetChanges() ?? new();
// Add an event handler to handle the errors during Update.
adapter.RowUpdated += OnRowUpdated;
connection.Open();
adapter.Update(dataSetChanges, "Customers");
connection.Close();
// Merge the updates.
dataSet.Merge(dataSetChanges, true, MissingSchemaAction.Add);
// Reject changes on rows with errors and clear the error.
DataRow[] errRows = dataSet.Tables["Customers"]!.GetErrors();
foreach (DataRow errRow in errRows)
{
errRow.RejectChanges();
errRow.RowError = null;
}
// Commit the changes.
dataSet.AcceptChanges();
Dim customers As DataTable = dataSet.Tables("Customers")
' Make modifications to the Customers table.
' Get changes to the DataSet.
Dim dataSetChanges As DataSet = dataSet.GetChanges()
' Add an event handler to handle the errors during Update.
AddHandler adapter.RowUpdated, New SqlRowUpdatedEventHandler(
AddressOf OnRowUpdated)
connection.Open()
adapter.Update(dataSetChanges, "Customers")
connection.Close()
' Merge the updates.
dataSet.Merge(dataSetChanges, True, MissingSchemaAction.Add)
' Reject changes on rows with errors and clear the error.
Dim errRows() As DataRow = dataSet.Tables("Customers").GetErrors()
Dim errRow As DataRow
For Each errRow In errRows
errRow.RejectChanges()
errRow.RowError = Nothing
Next
' Commit the changes.
dataSet.AcceptChanges()
protected static void OnRowUpdated(
object sender, SqlRowUpdatedEventArgs args)
{
if (args.Status == UpdateStatus.ErrorsOccurred)
{
args.Row.RowError = args.Errors!.Message;
args.Status = UpdateStatus.SkipCurrentRow;
}
}
Private Sub OnRowUpdated(
ByVal sender As Object, ByVal args As SqlRowUpdatedEventArgs)
If args.Status = UpdateStatus.ErrorsOccurred Then
args.Row.RowError = args.Errors.Message
args.Status = UpdateStatus.SkipCurrentRow
End If
End Sub