合并数据集内容
您可以使用 Merge 方法将 DataSet、DataTable 或 DataRow 数组的内容合并到现有的 DataSet
中。 若干因素和选项会影响将新数据合并到现有 DataSet
中的方式。
主键
如果从合并接收新数据和架构的表具有主键,则传入数据中的新行将匹配具有与传入数据中的这些行相同 Original 主键值的现有行。 如果传入架构中的列匹配现有架构的相应列,则会修改现有行中的数据。 将会忽略或添加与现有架构不匹配的列,具体根据 MissingSchemaAction 参数而定。 主键值与任何现有行都不匹配的新行将追加到现有表中。
如果传入行或现有行的行状态为 Added,则将使用 Current 行的 Added
主键值来匹配他们的主键值,这是因为不存在 Original
行版本。
如果传入表和现有表包含一个具有相同名称但不同数据类型的列,将引发异常,并引发 MergeFailed 的 DataSet
事件。 如果传入表和现有表都具有已定义的键,但是主键针对不同的列,将引发异常,并引发 MergeFailed
的 DataSet
事件。
如果从合并中接收新数据的表不具有主键,传入数据中的新行将无法与该表中的现有行匹配,而这些新行则会追加到现有表。
表名称和命名空间
可以选择为 DataTable 对象分配 Namespace 属性值。 如果分配了 Namespace 值,则一个 DataSet 可以包含多个具有相同 DataTable 值的 TableName 对象。 在合并操作期间,TableName 和 Namespace 都用于标识合并目标。 如果未分配 Namespace,则只使用 TableName 来标识合并目标。
备注
这种行为在 .NET Framework 2.0 版中发生了变化。 在 1.1 版中,虽然支持命名空间,但会在合并操作过程中被忽略。 因此,使用 DataSet 属性值的 Namespace 将具有不同的行为,具体取决于您运行哪个版本的 .NET Framework。 例如,假设您有两个 DataSets
,其中包含具有相同 DataTables
属性值但不同 TableName 属性值的 Namespace。 在 .NET Framework 1.1 版中,在合并两个 Namespace 对象时,将会忽略不同的 DataSet 名称。 但从 2.0 版开始,合并操作会导致在目标 DataTables
中创建两个新的 DataSet。 原始的 DataTables
将不受合并的影响。
PreserveChanges
在将 DataSet
、DataTable
或 DataRow
数组传递给 Merge
方法时,可以包括可选参数用以指定是否在现有 DataSet
中保留更改以及如何处理在传入数据中发现的新架构元素。 在传入数据后面,这些数据的第一个参数是一个布尔型标志 PreserveChanges,它指定是否在现有 DataSet
中保留更改。 如果 PreserveChanges
标志设置为 true
,则传入值不会覆盖现有行的 Current
行版本中的现有值。 如果 PreserveChanges
标志设置为 false
,则传入值将覆盖现有行的 Current
行版本中的现有值。 如果未指定 PreserveChanges
标志,默认情况下它将设置为 false
。 有关行版本的详细信息,请参阅行状态和行版本。
如果 PreserveChanges
为 true
,则现有行的 Current 行版本中将保持现有行中的数据,而现有行的 Original 行版本中的数据将由传入行的 Original
行版本中的数据覆盖。 现有行的 RowState 设置为 Modified。 存在以下例外:
如果现有行的
RowState
为Deleted
,则此RowState
将保持为Deleted
而不设置为Modified
。 在这种情况下,传入行中的数据将仍然存储在现有行的Original
行版本中,并覆盖现有行的Original
行版本(除非传入行的RowState
为Added
)。如果传入行的
RowState
为Added
,则现有行的Original
行版本中的数据将不会由传入行中的数据覆盖,因为传入行不具有Original
行版本。
如果 PreserveChanges
为 false
,则现有行中的 Current
和 Original
行版本将都由传入行中的数据覆盖,并且现有行的 RowState
将设置为传入行的 RowState
。 存在以下例外:
如果传入行的
RowState
为Unchanged
且现有行的RowState
为Modified
、Deleted
或Added
,则现有行的RowState
将设置为Modified
。如果传入行的
RowState
为Added
且现有行的RowState
为Unchanged
、Modified
或Deleted
,则现有行的RowState
将设置为Modified
。 此外,现有行的Original
行版本中的数据不会由传入行中的数据覆盖,因为传入行不具有Original
行版本。
MissingSchemaAction
您可以使用 MissingSchemaAction 方法的可选 Merge
参数来指定 Merge
将如何处理传入数据中不属于现有 DataSet
的架构元素。
下表说明用于 MissingSchemaAction
的选项。
MissingSchemaAction 选项 | 说明 |
---|---|
Add | 将新的架构信息添加到 DataSet 并用传入值填充新列。 这是默认值。 |
AddWithKey | 将新的架构和主键信息添加到 DataSet 并用传入值填充新列。 |
Error | 如果遇到不匹配的架构信息,则引发异常。 |
Ignore | 忽略新的架构信息。 |
约束
使用 Merge
方法时,所有新数据都添加到现有 DataSet
之前不会检查约束。 添加了数据后,会对 DataSet
中的当前值实施约束。 你必须确保你的代码可以处理可能因约束冲突而引发的任何异常。
设想有这样一种情况:DataSet
中的某一现有行是主键值为 1 的 Unchanged
。 在与 Modified
主键值为 2 且 Original
主键值为 1 的 Current
传入行进行合并期间,由于 Original
主键值不同,现有行和传入行将被视为不匹配。 不过,完成合并和检查完约束后,将会引发一个异常,因为 Current
主键值违反了主键列的唯一约束。
备注
在向包含自动递增列(如标识列)的数据库表中插入行时,插入时操作返回的标识列值可能不匹配 DataSet
中的值,从而导致追加而不是合并返回的行。 有关详细信息,请参阅检索标识或自动编号值。
下面的代码示例将两个具有不同架构的 DataSet
对象合并成一个 DataSet
,它具有两个传入 DataSet
对象的组合架构。
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
下面的代码示例采用带有更新的现有 DataSet
,并将这些更新传递给 DataAdapter
以便在数据源中处理。 随后将结果合并到原始 DataSet
中。 在拒绝导致错误的更改之后,合并的更改与 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