使用 DataAdapter 执行批处理操作

通过 ADO.NET 中的批处理支持,DataAdapter 可以将 DataSetDataTable 中的 INSERT、UPDATE 和 DELETE 操作分组发向服务器,而不是每次发送一项操作。 因为减少了与服务器的往返次数,通常可以大大提高性能。 SQL Server .NET 数据提供程序 (System.Data.SqlClient) 和 Oracle .NET 数据提供程序 (System.Data.OracleClient) 支持批量更新。

在 ADO.NET 的以前版本中用 DataSet 中的更改更新数据库时,UpdateDataAdapter 方法执行一次会向数据库中更新一行。 当该方法循环访问指定 DataTable 中的各行时,它会检查每个 DataRow 以查看其是否已被修改。 如果行已被修改,它会调用相应的 UpdateCommandInsertCommandDeleteCommand,具体取决于该行的 RowState 属性值。 每行更新都需要通过网络往返访问一次数据库。

从 ADO.NET 2.0 开始,DbDataAdapter 公开一个 UpdateBatchSize 属性。 将 UpdateBatchSize 设置为正整数值可使对数据库的更新以指定大小的批处理形式发送。 例如,将 UpdateBatchSize 设置为 10 可将 10 个单独的语句编成一组并作为单个批处理进行提交。 将 UpdateBatchSize 设置为 0 可使 DataAdapter 使用服务器能够处理的最大批大小。 将其设置为 1 可禁用批处理更新,因为这时一次只发送一行。

执行极大的批处理会降低性能。 因此,在实现应用程序前应进行测试以得到最佳的批大小。

使用 UpdateBatchSize 属性

启用批处理更新时,的 UpdatedRowSourceUpdateCommandInsertCommandDeleteCommand 属性值应设置为 NoneOutputParameters。 执行批处理更新时,命令的 UpdatedRowSourceFirstReturnedRecordBoth 属性值无效。

下面的过程演示 UpdateBatchSize 属性的用法。 该过程采用两个自变量,一个是 DataSet 对象,它具有表示 Production.ProductCategory 表中 ProductCategoryID 和 Name 字段的列;另一个是表示批大小的整数(批处理中的行数) 。 代码创建一个新的 SqlDataAdapter 对象,并设置其 UpdateCommandInsertCommandDeleteCommand 属性。 代码假定 DataSet 对象具有经过修改的行。 它设置 UpdateBatchSize 属性并执行更新。

Public Sub BatchUpdate( _
  ByVal dataTable As DataTable, ByVal batchSize As Int32)
    ' Assumes GetConnectionString() returns a valid connection string.
    Dim connectionString As String = GetConnectionString()

    ' Connect to the AdventureWorks database.
    Using connection As New SqlConnection(connectionString)
        ' Create a SqlDataAdapter.
        Dim adapter As New SqlDataAdapter()

        'Set the UPDATE command and parameters.
        adapter.UpdateCommand = New SqlCommand( _
          "UPDATE Production.ProductCategory SET " _
          & "Name=@Name WHERE ProductCategoryID=@ProdCatID;", _
          connection)
        adapter.UpdateCommand.Parameters.Add("@Name", _
          SqlDbType.NVarChar, 50, "Name")
        adapter.UpdateCommand.Parameters.Add("@ProdCatID",  _
          SqlDbType.Int, 4, " ProductCategoryID ")
        adapter.UpdateCommand.UpdatedRowSource = _
          UpdateRowSource.None

        'Set the INSERT command and parameter.
        adapter.InsertCommand = New SqlCommand( _
          "INSERT INTO Production.ProductCategory (Name) VALUES (@Name);", _
  connection)
        adapter.InsertCommand.Parameters.Add("@Name", _
          SqlDbType.NVarChar, 50, "Name")
        adapter.InsertCommand.UpdatedRowSource = _
          UpdateRowSource.None

        'Set the DELETE command and parameter.
        adapter.DeleteCommand = New SqlCommand( _
          "DELETE FROM Production.ProductCategory " _
          & "WHERE ProductCategoryID=@ProdCatID;", connection)
        adapter.DeleteCommand.Parameters.Add("@ProdCatID", _
           SqlDbType.Int, 4, " ProductCategoryID ")
        adapter.DeleteCommand.UpdatedRowSource = UpdateRowSource.None

        ' Set the batch size.
        adapter.UpdateBatchSize = batchSize

        ' Execute the update.
        adapter.Update(dataTable)
    End Using
End Sub
public static void BatchUpdate(DataTable dataTable,Int32 batchSize)
{
    // Assumes GetConnectionString() returns a valid connection string.
    string connectionString = GetConnectionString();

    // Connect to the AdventureWorks database.
    using (SqlConnection connection = new
      SqlConnection(connectionString))
    {

        // Create a SqlDataAdapter.
        SqlDataAdapter adapter = new SqlDataAdapter();

        // Set the UPDATE command and parameters.
        adapter.UpdateCommand = new SqlCommand(
            "UPDATE Production.ProductCategory SET "
            + "Name=@Name WHERE ProductCategoryID=@ProdCatID;",
            connection);
        adapter.UpdateCommand.Parameters.Add("@Name",
           SqlDbType.NVarChar, 50, "Name");
        adapter.UpdateCommand.Parameters.Add("@ProdCatID",
           SqlDbType.Int, 4, "ProductCategoryID");
         adapter.UpdateCommand.UpdatedRowSource = UpdateRowSource.None;

        // Set the INSERT command and parameter.
        adapter.InsertCommand = new SqlCommand(
            "INSERT INTO Production.ProductCategory (Name) VALUES (@Name);",
            connection);
        adapter.InsertCommand.Parameters.Add("@Name",
          SqlDbType.NVarChar, 50, "Name");
        adapter.InsertCommand.UpdatedRowSource = UpdateRowSource.None;

        // Set the DELETE command and parameter.
        adapter.DeleteCommand = new SqlCommand(
            "DELETE FROM Production.ProductCategory "
            + "WHERE ProductCategoryID=@ProdCatID;", connection);
        adapter.DeleteCommand.Parameters.Add("@ProdCatID",
          SqlDbType.Int, 4, "ProductCategoryID");
        adapter.DeleteCommand.UpdatedRowSource = UpdateRowSource.None;

        // Set the batch size.
        adapter.UpdateBatchSize = batchSize;

        // Execute the update.
        adapter.Update(dataTable);
    }
}

DataAdapter 包含两个与更新有关的事件:RowUpdating 和 RowUpdated 。 在 ADO.NET 的以前版本中,如果禁用批处理,则每处理一行就会生成一次这些事件。 RowUpdating 在更新发生之前生成,而 RowUpdated 在数据库更新完成之后生成。

批处理更新的事件行为更改

启用批处理时,在单个数据库操作中可更新多行。 因此,每个批处理只发生一次 RowUpdated 事件,而对于处理每一行,RowUpdating 事件都会发生。 禁用批处理时,这两个事件一对一交错触发,即一行触发一个 RowUpdating 事件和一个 RowUpdated 事件,下一行触发一个 RowUpdating 事件和一个 RowUpdated 事件,直到处理完所有行。

访问更新的行

禁用批处理时,可以使用 Row 类的 RowUpdatedEventArgs 属性访问要进行更新的行。

启用批处理时,会为多行生成单个 RowUpdated 事件。 因此,每一行的 Row 属性值为空。 但仍会为每一行生成 RowUpdating 事件。 使用 CopyToRows 类的 RowUpdatedEventArgs 方法可以通过将对行的引用复制到一个数组来访问已处理的行。 如果没有要进行处理的行,CopyToRows 将引发一个 ArgumentNullException。 在调用 RowCount 方法之前,使用 CopyToRows 属性可返回已处理行的数目。

处理数据错误

执行批处理与执行每个单独的语句具有相同的效果。 各语句按照其添加到批处理中的顺序执行。 在批处理模式下处理错误的方式与禁用批处理模式时相同。 每一行均单独处理。 只有在数据库中经过成功处理的行才能在 DataRow 内的相应 DataTable 中更新。

数据提供程序和后端数据库服务器确定支持哪些 SQL 构造以执行批处理。 如果为执行提交了不支持的语句,则可能引发异常。

另请参阅