Atualizando fontes de dados com DataAdapters

O Update método do é chamado para resolver alterações de um DataSet back para a fonte de DataAdapter dados. O Update método, como o Fill método, toma como argumentos uma instância de um DataSet, e um objeto opcional DataTable ou DataTable nome. A DataSet instância é a DataSet que contém as alterações que foram feitas e a identifica a DataTable tabela da qual recuperar as alterações. Se não DataTable for especificado, o primeiro DataTable do DataSet é usado.

Quando você chama o Update método, o DataAdapter analisa as alterações que foram feitas e executa o comando apropriado (INSERT, UPDATE ou DELETE). Quando o DataAdapter encontra uma alteração para um DataRow, ele usa o InsertCommand, UpdateCommandou DeleteCommand para processar a alteração. Isso permite maximizar o desempenho do seu aplicativo ADO.NET especificando a sintaxe de comando em tempo de design e, sempre que possível, através do uso de procedimentos armazenados. Você deve definir explicitamente os comandos antes de chamar Update. Se Update for chamado e o comando apropriado não existir para uma atualização específica (por exemplo, não DeleteCommand para linhas excluídas), uma exceção será lançada.

Nota

Se você estiver usando procedimentos armazenados do SQL Server para editar ou excluir dados usando um DataAdapter, certifique-se de não usar SET NOCOUNT ON na definição de procedimento armazenado. Isso faz com que a contagem de linhas afetadas retornada seja zero, o que o DataAdapter interpreta como um conflito de simultaneidade. Neste caso, um DBConcurrencyException será lançado.

Os parâmetros de comando podem ser usados para especificar valores de entrada e saída para uma instrução SQL ou procedimento armazenado para cada linha modificada em um DataSetarquivo . Para obter mais informações, consulte Parâmetros DataAdapter.

Nota

É importante entender a diferença entre excluir uma linha em um DataTable e remover a linha. Quando você chama o Remove método ou RemoveAt , a linha é removida imediatamente. Quaisquer linhas correspondentes na fonte de dados de back-end não serão afetadas se você passar o DataTable ou para um DataAdapter e chamar UpdateDataSet . Quando você usa o Delete método, a linha permanece no e é marcada DataTable para exclusão. Se você passar o DataTable ou DataSet para um DataAdapter e chamar Update, a linha correspondente na fonte de dados de back-end será excluída.

Se seus DataTable mapas para ou forem gerados a partir de uma única tabela de banco de dados, você poderá aproveitar o DbCommandBuilder objeto para gerar automaticamente os DeleteCommandobjetos , InsertCommande UpdateCommand para o DataAdapter. Para obter mais informações, consulte Gerando comandos com CommandBuilders.

Usando UpdatedRowSource para mapear valores para um DataSet

Você pode controlar como os valores retornados da fonte de dados são mapeados de volta para a DataTable seguinte chamada para o método Update de um DataAdapter, usando a UpdatedRowSource propriedade de um DbCommand objeto. Ao definir a UpdatedRowSource propriedade como um dos valores de enumeração, você pode controlar se os DataAdapter parâmetros de saída retornados UpdateRowSource pelos comandos são ignorados ou aplicados à linha alterada no DataSet. Você também pode especificar se a primeira linha retornada (se existir) é aplicada à linha alterada no DataTable.

A tabela a seguir descreve os diferentes valores da UpdateRowSource enumeração e como eles afetam o comportamento de um comando usado com um DataAdapter.

Enumeração UpdatedRowSource Description
Both Os parâmetros de saída e a primeira linha de um conjunto de resultados retornado podem ser mapeados para a linha alterada no DataSet.
FirstReturnedRecord Somente os dados na primeira linha de um conjunto de resultados retornado podem ser mapeados para a linha alterada no DataSet.
None Todos os parâmetros de saída ou linhas de um conjunto de resultados retornado são ignorados.
OutputParameters Somente os parâmetros de saída podem ser mapeados para a linha alterada no DataSet.

O Update método resolve suas alterações de volta para a fonte de dados, no entanto, outros clientes podem ter modificado dados na fonte de dados desde a última vez que você preencheu o DataSet. Para atualizar os DataSet dados atuais, use o DataAdapter método and Fill . Novas linhas serão adicionadas à tabela e informações atualizadas serão incorporadas às linhas existentes. O Fill método determina se uma nova linha será adicionada ou uma linha existente será atualizada examinando os valores de chave primária das linhas no DataSet e as linhas retornadas pelo SelectCommand. Se o Fill método encontrar um valor de chave primária para uma linha na que corresponde a DataSet um valor de chave primária de uma linha nos resultados retornados pelo SelectCommand, ele atualiza a linha existente com as informações da linha retornada pelo SelectCommand e define o RowState da linha existente como Unchanged. Se uma linha retornada pelo SelectCommand tiver um valor de chave primária que não corresponda a nenhum dos valores de chave primária das linhas no DataSet, o Fill método adicionará uma nova linha com um RowState de Unchanged.

Nota

Se o SelectCommand retorna os resultados de um OUTER JOIN, o DataAdapter não definirá um PrimaryKey valor para o resultado DataTable. Você deve definir a PrimaryKey si mesmo para garantir que as linhas duplicadas sejam resolvidas corretamente. Para obter mais informações, consulte Definindo chaves primárias.

Para lidar com exceções que podem ocorrer ao chamar o Update método, você pode usar o RowUpdated evento para responder a erros de atualização de linha à medida que eles ocorrem (consulte Manipulando eventos DataAdapter), ou você pode definir DataAdapter.ContinueUpdateOnError como true antes de chamar Update, e responder às informações de erro armazenadas na RowError propriedade de uma linha específica quando a atualização estiver concluída (consulte Informações de erro de linha).

Nota

Chamar o , ou fará com que todos os Original valores de a DataRow sejam substituídos pelos Current valores do DataRow.DataRowDataTableDataSetAcceptChanges Se os valores de campo que identificam a linha como exclusiva tiverem sido modificados, depois de chamar AcceptChanges os Original valores não corresponderão mais aos valores na fonte de dados. AcceptChanges é chamado automaticamente para cada linha durante uma chamada para o método Update de um DataAdapter. Você pode preservar os valores originais durante uma chamada para o método Update definindo primeiro a AcceptChangesDuringUpdate propriedade do to false, ou criando um manipulador de DataAdapter eventos para o RowUpdated evento e definindo o Status como SkipCurrentRow. Para obter mais informações, consulte Mesclando conteúdo de DataSet e Manipulando eventos DataAdapter.

Exemplo

Os exemplos a seguir demonstram como executar atualizações em linhas modificadas definindo explicitamente o UpdateCommand de a DataAdapter e chamando seu Update método. Observe que o parâmetro especificado na cláusula WHERE da instrução UPDATE está definido para usar o Original valor do SourceColumn. Isso é importante, porque o Current valor pode ter sido modificado e pode não corresponder ao valor na fonte de dados. O Original valor é o valor que foi usado para preencher o DataTable da fonte de dados.

static void AdapterUpdate(string connectionString)
{
    using (SqlConnection connection =
               new(connectionString))
    {
        SqlDataAdapter dataAdapter = new(
          "SELECT CategoryID, CategoryName FROM Categories",
          connection)
        {
            UpdateCommand = new SqlCommand(
           "UPDATE Categories SET CategoryName = @CategoryName " +
           "WHERE CategoryID = @CategoryID", connection)
        };

        dataAdapter.UpdateCommand.Parameters.Add(
           "@CategoryName", SqlDbType.NVarChar, 15, "CategoryName");

        SqlParameter parameter = dataAdapter.UpdateCommand.Parameters.Add(
          "@CategoryID", SqlDbType.Int);
        parameter.SourceColumn = "CategoryID";
        parameter.SourceVersion = DataRowVersion.Original;

        DataTable categoryTable = new();
        dataAdapter.Fill(categoryTable);

        DataRow categoryRow = categoryTable.Rows[0];
        categoryRow["CategoryName"] = "New Beverages";

        dataAdapter.Update(categoryTable);

        Console.WriteLine("Rows after update.");
        foreach (DataRow row in categoryTable.Rows)
        {
            {
                Console.WriteLine("{0}: {1}", row[0], row[1]);
            }
        }
    }
}
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

Colunas de AutoIncremento

Se as tabelas da fonte de dados tiverem colunas de incremento automático, você poderá preencher as colunas retornando DataSet o valor de incremento automático como um parâmetro de saída de um procedimento armazenado e mapeando-o para uma coluna em uma tabela, retornando o valor de incremento automático na primeira linha de um conjunto de resultados retornado por um procedimento armazenado ou instrução SQL, ou usando o RowUpdated evento do DataAdapter para executar uma instrução SELECT adicional. Para obter mais informações e um exemplo, consulte Recuperando valores de identidade ou de numeração automática.

Ordenação de inserções, atualizações e exclusões

Em muitas circunstâncias, a ordem em que as alterações feitas através do DataSet são enviadas para a fonte de dados é importante. Por exemplo, se um valor de chave primária para uma linha existente for atualizado e uma nova linha tiver sido adicionada com o novo valor de chave primária como uma chave estrangeira, é importante processar a atualização antes da inserção.

Você pode usar o Select método do DataTable para retornar uma DataRow matriz que só faz referência a linhas com um determinado RowState. Em seguida, você pode passar a matriz retornada DataRow para o Update método do DataAdapter para processar as linhas modificadas. Ao especificar um subconjunto de linhas a serem atualizadas, você pode controlar a ordem na qual as inserções, atualizações e exclusões são processadas.

Por exemplo, o código a seguir garante que as linhas excluídas da tabela sejam processadas primeiro, depois as linhas atualizadas e, em seguida, as linhas inseridas.

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.
adapter.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));

Usar um DataAdapter para recuperar e atualizar dados

Você pode usar um DataAdapter para recuperar e atualizar os dados.

  • O exemplo usa DataAdapter.AcceptChangesDuringFill para clonar os dados no banco de dados. Se a propriedade for definida como false, AcceptChanges não será chamado ao preencher a tabela e as linhas recém-adicionadas serão tratadas como linhas inseridas. Portanto, o exemplo usa essas linhas para inserir as novas linhas no banco de dados.

  • Os exemplos usam DataAdapter.TableMappings para definir o mapeamento entre a tabela de origem e DataTable.

  • O exemplo usa DataAdapter.FillLoadOption para determinar como o adaptador preenche a DataTable do DbDataReader. Quando você cria uma DataTable, você só pode gravar os dados do banco de dados para a versão atual ou a versão original definindo a propriedade como LoadOption.Upsert ou LoadOption.PreserveChanges.

  • O exemplo também atualizará a tabela usando DbDataAdapter.UpdateBatchSize para executar operações em lote.

Antes de compilar e executar o exemplo, você precisa criar o banco de dados de exemplo:

USE [master]
GO

CREATE DATABASE [MySchool]

GO

USE [MySchool]
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Course]([CourseID] [nvarchar](10) NOT NULL,
[Year] [smallint] NOT NULL,
[Title] [nvarchar](100) NOT NULL,
[Credits] [int] NOT NULL,
[DepartmentID] [int] NOT NULL,
 CONSTRAINT [PK_Course] PRIMARY KEY CLUSTERED
(
[CourseID] ASC,
[Year] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY]

GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Department]([DepartmentID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NOT NULL,
[Budget] [money] NOT NULL,
[StartDate] [datetime] NOT NULL,
[Administrator] [int] NULL,
 CONSTRAINT [PK_Department] PRIMARY KEY CLUSTERED
(
[DepartmentID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY]

GO

INSERT [dbo].[Course] ([CourseID], [Year], [Title], [Credits], [DepartmentID]) VALUES (N'C1045', 2012, N'Calculus', 4, 7)
INSERT [dbo].[Course] ([CourseID], [Year], [Title], [Credits], [DepartmentID]) VALUES (N'C1061', 2012, N'Physics', 4, 1)
INSERT [dbo].[Course] ([CourseID], [Year], [Title], [Credits], [DepartmentID]) VALUES (N'C2021', 2012, N'Composition', 3, 2)
INSERT [dbo].[Course] ([CourseID], [Year], [Title], [Credits], [DepartmentID]) VALUES (N'C2042', 2012, N'Literature', 4, 2)

SET IDENTITY_INSERT [dbo].[Department] ON

INSERT [dbo].[Department] ([DepartmentID], [Name], [Budget], [StartDate], [Administrator]) VALUES (1, N'Engineering', 350000.0000, CAST(0x0000999C00000000 AS DateTime), 2)
INSERT [dbo].[Department] ([DepartmentID], [Name], [Budget], [StartDate], [Administrator]) VALUES (2, N'English', 120000.0000, CAST(0x0000999C00000000 AS DateTime), 6)
INSERT [dbo].[Department] ([DepartmentID], [Name], [Budget], [StartDate], [Administrator]) VALUES (4, N'Economics', 200000.0000, CAST(0x0000999C00000000 AS DateTime), 4)
INSERT [dbo].[Department] ([DepartmentID], [Name], [Budget], [StartDate], [Administrator]) VALUES (7, N'Mathematics', 250024.0000, CAST(0x0000999C00000000 AS DateTime), 3)
SET IDENTITY_INSERT [dbo].[Department] OFF

ALTER TABLE [dbo].[Course]  WITH CHECK ADD  CONSTRAINT [FK_Course_Department] FOREIGN KEY([DepartmentID])
REFERENCES [dbo].[Department] ([DepartmentID])
GO
ALTER TABLE [dbo].[Course] CHECK CONSTRAINT [FK_Course_Department]
GO
using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using CSDataAdapterOperations.Properties;

namespace CSDataAdapterOperations.Properties {
   internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {

      private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));

      public static Settings Default {
         get {
            return defaultInstance;
         }
      }

      [global::System.Configuration.ApplicationScopedSettingAttribute()]
      [global::System.Configuration.DefaultSettingValueAttribute("Data Source=(local);Initial Catalog=MySchool;Integrated Security=True")]
      public string MySchoolConnectionString {
         get {
            return ((string)(this["MySchoolConnectionString"]));
         }
      }
   }
}

class Program {
   static void Main(string[] args) {
      Settings settings = new Settings();

      // Copy the data from the database.  Get the table Department and Course from the database.
      String selectString = @"SELECT [DepartmentID],[Name],[Budget],[StartDate],[Administrator]
                                     FROM [MySchool].[dbo].[Department];

                                   SELECT [CourseID],@Year as [Year],Max([Title]) as [Title],
                                   Max([Credits]) as [Credits],Max([DepartmentID]) as [DepartmentID]
                                   FROM [MySchool].[dbo].[Course]
                                   Group by [CourseID]";

      DataSet mySchool = new DataSet();

      SqlCommand selectCommand = new SqlCommand(selectString);
      SqlParameter parameter = selectCommand.Parameters.Add("@Year", SqlDbType.SmallInt, 2);
      parameter.Value = new Random(DateTime.Now.Millisecond).Next(9999);

      // Use DataTableMapping to map the source tables and the destination tables.
      DataTableMapping[] tableMappings = {new DataTableMapping("Table", "Department"), new DataTableMapping("Table1", "Course")};
      CopyData(mySchool, settings.MySchoolConnectionString, selectCommand, tableMappings);

      Console.WriteLine("The following tables are from the database.");
      foreach (DataTable table in mySchool.Tables) {
         Console.WriteLine(table.TableName);
         ShowDataTable(table);
      }

      // Roll back the changes
      DataTable department = mySchool.Tables["Department"];
      DataTable course = mySchool.Tables["Course"];

      department.Rows[0]["Name"] = "New" + department.Rows[0][1];
      course.Rows[0]["Title"] = "New" + course.Rows[0]["Title"];
      course.Rows[0]["Credits"] = 10;

      Console.WriteLine("After we changed the tables:");
      foreach (DataTable table in mySchool.Tables) {
         Console.WriteLine(table.TableName);
         ShowDataTable(table);
      }

      department.RejectChanges();
      Console.WriteLine("After use the RejectChanges method in Department table to roll back the changes:");
      ShowDataTable(department);

      DataColumn[] primaryColumns = { course.Columns["CourseID"] };
      DataColumn[] resetColumns = { course.Columns["Title"] };
      ResetCourse(course, settings.MySchoolConnectionString, primaryColumns, resetColumns);
      Console.WriteLine("After use the ResetCourse method in Course table to roll back the changes:");
      ShowDataTable(course);

      // Batch update the table.
      String insertString = @"Insert into [MySchool].[dbo].[Course]([CourseID],[Year],[Title],
                                   [Credits],[DepartmentID])
             values (@CourseID,@Year,@Title,@Credits,@DepartmentID)";
      SqlCommand insertCommand = new SqlCommand(insertString);
      insertCommand.Parameters.Add("@CourseID", SqlDbType.NVarChar, 10, "CourseID");
      insertCommand.Parameters.Add("@Year", SqlDbType.SmallInt, 2, "Year");
      insertCommand.Parameters.Add("@Title", SqlDbType.NVarChar, 100, "Title");
      insertCommand.Parameters.Add("@Credits", SqlDbType.Int, 4, "Credits");
      insertCommand.Parameters.Add("@DepartmentID", SqlDbType.Int, 4, "DepartmentID");

      const Int32 batchSize = 10;
      BatchInsertUpdate(course, settings.MySchoolConnectionString, insertCommand, batchSize);
   }

   private static void CopyData(DataSet dataSet, String connectionString, SqlCommand selectCommand, DataTableMapping[] tableMappings) {
      using (SqlConnection connection = new SqlConnection(connectionString)) {
         selectCommand.Connection = connection;

         connection.Open();

         using (SqlDataAdapter adapter = new SqlDataAdapter(selectCommand)) {adapter.TableMappings.AddRange(tableMappings);
            // If set the AcceptChangesDuringFill as the false, AcceptChanges will not be called on a
            // DataRow after it is added to the DataTable during any of the Fill operations.
            adapter.AcceptChangesDuringFill = false;

            adapter.Fill(dataSet);
         }
      }
   }

   // Roll back only one column or several columns data of the Course table by call ResetDataTable method.
   private static void ResetCourse(DataTable table, String connectionString,
       DataColumn[] primaryColumns, DataColumn[] resetColumns) {
      table.PrimaryKey = primaryColumns;

      // Build the query string
      String primaryCols = String.Join(",", primaryColumns.Select(col => col.ColumnName));
      String resetCols = String.Join(",", resetColumns.Select(col => $"Max({col.ColumnName}) as {col.ColumnName}"));

      String selectString = $"Select {primaryCols},{resetCols} from Course Group by {primaryCols}");

      SqlCommand selectCommand = new SqlCommand(selectString);

      ResetDataTable(table, connectionString, selectCommand);
   }

   // RejectChanges will roll back all changes made to the table since it was loaded, or the last time AcceptChanges
   // was called. When you copy from the database, you can lose all the data after calling RejectChanges
   // The ResetDataTable method rolls back one or more columns of data.
   private static void ResetDataTable(DataTable table, String connectionString,
       SqlCommand selectCommand) {
      using (SqlConnection connection = new SqlConnection(connectionString)) {
         selectCommand.Connection = connection;

         connection.Open();

         using (SqlDataAdapter adapter = new SqlDataAdapter(selectCommand)) {
            // The incoming values for this row will be written to the current version of each
            // column. The original version of each column's data will not be changed.
            adapter.FillLoadOption = LoadOption.Upsert;

            adapter.Fill(table);
         }
      }
   }

   private static void BatchInsertUpdate(DataTable table, String connectionString,
       SqlCommand insertCommand, Int32 batchSize) {
      using (SqlConnection connection = new SqlConnection(connectionString)) {
         insertCommand.Connection = connection;
         // When setting UpdateBatchSize to a value other than 1, all the commands
         // associated with the SqlDataAdapter have to have their UpdatedRowSource
         // property set to None or OutputParameters. An exception is thrown otherwise.
         insertCommand.UpdatedRowSource = UpdateRowSource.None;

         connection.Open();

         using (SqlDataAdapter adapter = new SqlDataAdapter()) {
            adapter.InsertCommand = insertCommand;
            // Gets or sets the number of rows that are processed in each round-trip to the server.
            // Setting it to 1 disables batch updates, as rows are sent one at a time.
            adapter.UpdateBatchSize = batchSize;

            adapter.Update(table);

            Console.WriteLine("Successfully to update the table.");
         }
      }
   }

   private static void ShowDataTable(DataTable table) {
      foreach (DataColumn col in table.Columns) {
         Console.Write("{0,-14}", col.ColumnName);
      }
      Console.WriteLine("{0,-14}", "RowState");

      foreach (DataRow row in table.Rows) {
         foreach (DataColumn col in table.Columns) {
            if (col.DataType.Equals(typeof(DateTime)))
               Console.Write("{0,-14:d}", row[col]);
            else if (col.DataType.Equals(typeof(Decimal)))
               Console.Write("{0,-14:C}", row[col]);
            else
               Console.Write("{0,-14}", row[col]);
         }
         Console.WriteLine("{0,-14}", row.RowState);
      }
   }
}

Consulte também