Executando operações em lote usando DataAdapters

O suporte a lotes no ADO.NET permite que um DataAdapter agrupe operações INSERT, UPDATE e DELETE de um DataSet ou DataTable para o servidor, em vez de enviar uma operação de cada vez. A redução no número de viagens de ida e volta para o servidor costuma resultar em ganhos significativos de desempenho. Atualizações em lotes têm suporte nos provedores de dados .NET para SQL Server (System.Data.SqlClient) e Oracle (System.Data.OracleClient).

Ao atualizar um banco de dados com alterações de um DataSet em versões anteriores do ADO.NET, o método Update de um DataAdapter executava atualizações no banco de dados em uma linha por vez. Ao iterar pelas linhas no DataTable especificado, ele revisava cada DataRow para verificar se ocorreram modificações. Se a linha tivesse sido alterada, ele chamava o UpdateCommand, InsertCommand ou DeleteCommand apropriado, dependendo do valor da propriedade RowState para essa linha. Cada atualização de linha envolvia uma viagem de ida e volta da rede ao banco de dados.

A partir do ADO.NET 2.0, o DbDataAdapter expõe uma propriedade UpdateBatchSize. Definir o UpdateBatchSize com um valor inteiro positivo causa o envio de atualizações do banco de dados como lotes de tamanho especificado. Por exemplo, a definição do UpdateBatchSize como 10 agrupará 10 instruções separadas e as submeterá como um único lote. Definir o UpdateBatchSize como 0 fará com que o DataAdapter use o maior tamanho de lotes que o servidor possa manipular. Defini-lo como 1 desabilitará atualizações em lotes, pois as linhas são enviadas uma de cada vez.

Executar um lote extremamente grande pode diminuir o desempenho. Portanto, você deve testar para verificar qual é a melhor configuração de tamanho de lote antes de implementar seu aplicativo.

Usando a propriedade UpdateBatchSize

Quando atualizações em lotes são habilitadas, o valor da propriedade UpdatedRowSource de UpdateCommand, de InsertCommand e de DeleteCommand do DataAdapter deve ser definido como None ou OutputParameters. Ao executar uma atualização em lotes, o valor da propriedade UpdatedRowSource do comando de FirstReturnedRecord ou de Both é inválido.

O procedimento a seguir demonstra o uso da propriedade UpdateBatchSize. O procedimento tem dois argumentos, um objeto DataSet com colunas que representam os campos ProductCategoryID e Name na tabela Production.ProductCategory e um inteiro que representa o tamanho do lote (o número de linhas no lote). O código cria um novo objeto SqlDataAdapter, definindo suas propriedades UpdateCommand, InsertCommand e DeleteCommand. O código pressupõe que o objeto DataSet alterou linhas. Ele define a propriedade UpdateBatchSize e executa a atualização.

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);  
    }  
}  

O DataAdapter tem dois eventos relacionados à atualização: RowUpdating e RowUpdated. Em versões anteriores do ADO.NET, quando o processamento em lotes estava desabilitado, cada um desses eventos era gerado uma vez para cada linha processada. RowUpdating é gerado antes de a atualização ocorrer, e RowUpdated é gerado após a conclusão da atualização do banco de dados.

Alterações de comportamento dos eventos com atualizações em lotes

Quando o processamento em lotes está habilitado, várias linhas são atualizadas em uma única operação de banco de dados. Portanto, somente um evento RowUpdated ocorre para cada lote, enquanto o evento RowUpdating ocorre para cada linha processada. Quando o processamento em lotes está desabilitado, os dois eventos são disparados com interpolação um a um, onde um evento RowUpdating e um evento RowUpdated são disparados para uma linha e, depois, um evento RowUpdating e um evento RowUpdated são disparados para a próxima linha, até que todas as linhas sejam processadas.

Acessando linhas atualizadas

Quando o processamento em lotes está desabilitado, a linha que está sendo atualizada pode ser acessada usando a propriedade Row da classe RowUpdatedEventArgs.

Quando o processamento em lotes está habilitado, um único evento RowUpdated é gerado para várias linhas. Portanto, o valor da propriedade Row para cada linha é nulo. Os eventos RowUpdating ainda são gerados para cada linha. O método CopyToRows da classe RowUpdatedEventArgs permite que você acesse as linhas processadas copiando referências às linhas em uma matriz. Se nenhuma linha está sendo processada, CopyToRows gera ArgumentNullException. Use a propriedade RowCount para retornar o número de linhas processadas antes de chamar o método CopyToRows.

Manipulando erros de dados

A execução em lotes tem o mesmo efeito que a execução de cada instrução individual. As instruções são executadas na ordem em que elas foram adicionados ao lote. O tratamento de erros no modo em lotes é o mesmo de quando esse modo está desabilitado. Cada linha é processada separadamente. Somente linhas que foram processadas com êxito no banco de dados serão atualizadas na DataRow correspondente dentro da DataTable.

O provedor de dados e o servidor de banco de dados back-end determinam que construções SQL têm suporte para a execução em lotes. Uma exceção pode ser gerada quando uma instrução sem suporte é enviada para execução.

Confira também