使用 DataAdapter 更新数据源 (ADO.NET)
调用 DataAdapter 的 Update 方法可以将 DataSet 中的更改解析回数据源。 与 Fill 方法类似,Update 方法将 DataSet 的实例和可选的 DataTable 对象或 DataTable 名称用作参数。 DataSet 实例是包含已做的更改的 DataSet,DataTable 标识从其中检索这些更改的表。 如果未指定 DataTable,则使用 DataSet 中的第一个 DataTable。
当调用 Update 方法时,DataAdapter 会分析已做的更改并执行相应的命令(INSERT、UPDATE 或 DELETE)。 当 DataAdapter 遇到对 DataRow 所做的更改时,它将使用 InsertCommand、UpdateCommand 或 DeleteCommand 来处理该更改。 这样,您就可以通过在设计时指定命令语法并在可能时通过使用存储过程来尽量提高 ADO.NET 应用程序的性能。 在调用 Update 之前,必须显式设置这些命令。 如果调用了 Update 但不存在用于特定更新的相应命令(例如,不存在用于已删除行的 DeleteCommand),则会引发异常。
注意 |
---|
如果您要通过 SQL Server 存储过程使用 DataAdapter 来编辑或删除数据,请确保不要在存储过程定义中使用 SET NOCOUNT ON。这将使返回的受影响的行数为零,DataAdapter 会将其解释为并发冲突。在这种情况下,将引发 DBConcurrencyException。 |
可以使用命令参数为 DataSet 中每个已修改的行指定 SQL 语句或存储过程的输入和输出值。 有关更多信息,请参见DataAdapter 参数 (ADO.NET)。
注意 |
---|
必须了解在 DataTable 中删除行和移除行之间的差异。当调用 Remove 或 RemoveAt 方法时,会立即移除该行。如果之后将 DataTable 或 DataSet 传递给 DataAdapter 并调用 Update,则不会影响后端数据源中的任何相应行。当您使用 Delete 方法时,该行仍将保留在 DataTable 中并会标记为删除。如果之后将 DataTable 或 DataSet 传递给 DataAdapter 并调用 Update,则会删除后端数据源中的相应行。 |
如果 DataTable 映射到单个数据库表或从单个数据库表生成,则可以利用 DbCommandBuilder 对象为 DataAdapter 自动生成 DeleteCommand、InsertCommand 和 UpdateCommand 对象。 有关更多信息,请参见使用 CommandBuilder 生成命令 (ADO.NET)。
使用 UpdatedRowSource 将值映射到数据集
通过使用 DbCommand 对象的 UpdatedRowSource 属性,您可以在调用 DataAdapter 的 Update 方法后控制从数据源返回的值映射回 DataTable 的方式。 通过将 UpdatedRowSource 属性设置为 UpdateRowSource 枚举值之一,您可以控制是忽略由 DataAdapter 命令返回的输出参数还是将其应用于 DataSet 中已更改的行。 还可以指定是否将返回的第一行(如果存在)应用于 DataTable 中已更改的行。
下表说明 UpdateRowSource 枚举的不同值,并说明它们如何影响与 DataAdapter 一起使用的命令的行为。
UpdatedRowSource 枚举 |
说明 |
---|---|
输出参数和返回的结果集的第一行都可以映射到 DataSet 中已更改的行。 |
|
只有返回的结果集的第一行中的数据才可以映射到 DataSet 中已更改的行。 |
|
忽略任何输出参数或返回的结果集中的行。 |
|
只有输出参数才可以映射到 DataSet 中已更改的行。 |
Update 方法会将更改解析回数据源;但在上次填充 DataSet 后,其他客户端可能已修改了数据源中的数据。 若要使用当前数据刷新 DataSet,请使用 DataAdapter 和 Fill 方法。 新行将添加到该表中,更新的信息将并入现有行。 Fill 方法通过检查 DataSet 中行的主键值以及 SelectCommand 返回的行来确定是要添加新行还是更新现有行。 如果 Fill 方法遇到 DataSet 中某行的主键值与 SelectCommand 返回结果中某行的主键值相匹配,则它将用 SelectCommand 返回的行中的信息更新现有行,并将现有行的 RowState 设置为 Unchanged。 如果 SelectCommand 返回的行所具有的主键值与 DataSet 中行的任何主键值都不匹配,则 Fill 方法将添加 RowState 为 Unchanged 的新行。
注意 |
---|
如果 SelectCommand 返回 OUTER JOIN 的结果,则 DataAdapter 不会为生成的 DataTable 设置 PrimaryKey 值。您必须自己定义 PrimaryKey 以确保正确解析重复行。有关更多信息,请参见定义主键 (ADO.NET)。 |
若要处理在调用 Update方法时可能发生的异常,可以使用 RowUpdated 事件响应更新行时发生的错误(请参见处理 DataAdapter 事件 (ADO.NET)),也可以在调用 Update 之前将 DataAdapter.ContinueUpdateOnError 设置为 true,并在更新完成后响应特定行的 RowError 属性中存储的错误信息(请参见行错误信息)。
注意 对 DataSet、DataTable 或 DataRow 调用 AcceptChanges 将导致 DataRow 的所有 Original 值被 DataRow 的 Current 值覆盖。 如果修改了唯一标识该行的字段值,则在调用 AcceptChanges 后,Original 值将不再匹配数据源中的值。 在调用 DataAdapter 的 Update 方法期间会对每一行自动调用 AcceptChanges。 在调用 Update 方法期间,通过先将 DataAdapter 的 AcceptChangesDuringUpdate 属性设置为 false,或为 RowUpdated 事件创建一个事件处理程序并将 Status 设置为 SkipCurrentRow,可以保留原始值。 有关更多信息,请参见合并数据集内容 (ADO.NET)和处理 DataAdapter 事件 (ADO.NET)。
示例
下面的示例演示如何通过显式设置 DataAdapter 的 UpdateCommand 并调用其 Update 方法对已修改行的执行更新。 请注意,在 UPDATE 语句的 WHERE 子句中指定的参数设置为使用 SourceColumn 的 Original 值。 这一点很重要,因为 Current 值可能已被修改,可能会不匹配数据源中的值。 Original 值是用于从数据源填充 DataTable 的值。
Private Sub AdapterUpdate(ByVal connectionString As String)
Using connection As SqlConnection = New SqlConnection( _
connectionString)
Dim adapter As SqlDataAdapter = New SqlDataAdapter( _
"SELECT CategoryID, CategoryName FROM dbo.Categories", _
connection)
adapter.UpdateCommand = New SqlCommand( _
"UPDATE Categories SET CategoryName = @CategoryName " & _
"WHERE CategoryID = @CategoryID", connection)
adapter.UpdateCommand.Parameters.Add( _
"@CategoryName", SqlDbType.NVarChar, 15, "CategoryName")
Dim parameter As SqlParameter = _
adapter.UpdateCommand.Parameters.Add( _
"@CategoryID", SqlDbType.Int)
parameter.SourceColumn = "CategoryID"
parameter.SourceVersion = DataRowVersion.Original
Dim categoryTable As New DataTable
adapter.Fill(categoryTable)
Dim categoryRow As DataRow = categoryTable.Rows(0)
categoryRow("CategoryName") = "New Beverages"
adapter.Update(categoryTable)
Console.WriteLine("Rows after update.")
Dim row As DataRow
For Each row In categoryTable.Rows
Console.WriteLine("{0}: {1}", row(0), row(1))
Next
End Using
End Sub
private static void AdapterUpdate(string connectionString)
{
using (SqlConnection connection =
new SqlConnection(connectionString))
{
SqlDataAdapter dataAdpater = new SqlDataAdapter(
"SELECT CategoryID, CategoryName FROM Categories",
connection);
dataAdpater.UpdateCommand = new SqlCommand(
"UPDATE Categories SET CategoryName = @CategoryName " +
"WHERE CategoryID = @CategoryID", connection);
dataAdpater.UpdateCommand.Parameters.Add(
"@CategoryName", SqlDbType.NVarChar, 15, "CategoryName");
SqlParameter parameter = dataAdpater.UpdateCommand.Parameters.Add(
"@CategoryID", SqlDbType.Int);
parameter.SourceColumn = "CategoryID";
parameter.SourceVersion = DataRowVersion.Original;
DataTable categoryTable = new DataTable();
dataAdpater.Fill(categoryTable);
DataRow categoryRow = categoryTable.Rows[0];
categoryRow["CategoryName"] = "New Beverages";
dataAdpater.Update(categoryTable);
Console.WriteLine("Rows after update.");
foreach (DataRow row in categoryTable.Rows)
{
{
Console.WriteLine("{0}: {1}", row[0], row[1]);
}
}
}
}
AutoIncrement 列
如果数据源中的表具有自动递增列,则可以通过以下方式填充 DataSet 中的列:作为存储过程的输出参数返回自动递增值并将其映射到表中的一列、返回由存储过程或 SQL 语句返回的结果集第一行中的自动递增值或者使用 DataAdapter 的 RowUpdated 事件来执行其他 SELECT 语句。 有关更多信息和示例,请参见检索标识或 Autonumber 值 (ADO.NET)。
插入、更新和删除的排序
在许多情况下,以何种顺序向数据源发送通过 DataSet 所做的更改是非常重要的。 例如,如果更新了现有行的主键值,并且添加了以新主键值作为外键的新行,则务必要在处理插入之前处理更新。
可以使用 DataTable 的 Select 方法来返回仅引用具有特定 RowState 的 DataRow 数组。 然后可以将返回的 DataRow 数组传递给 DataAdapter 的 Update 方法来处理已修改的行。 通过指定要更新的行的子集,可以控制处理插入、更新和删除的顺序。
示例
例如,以下代码确保首先处理表中已删除的行,然后处理已更新的行,然后处理已插入的行。
Dim table As DataTable = dataSet.Tables("Customers")
' First process deletes.
dataSet.Update(table.Select(Nothing, Nothing, _
DataViewRowState.Deleted))
' Next process updates.
adapter.Update(table.Select(Nothing, Nothing, _
DataViewRowState.ModifiedCurrent))
' Finally, process inserts.
dataAdpater.Update(table.Select(Nothing, Nothing, _
DataViewRowState.Added))
DataTable table = dataSet.Tables["Customers"];
// First process deletes.
adapter.Update(table.Select(null, null, DataViewRowState.Deleted));
// Next process updates.
adapter.Update(table.Select(null, null,
DataViewRowState.ModifiedCurrent));
// Finally, process inserts.
adapter.Update(table.Select(null, null, DataViewRowState.Added));