Partilhar via


Atualizar fontes de dados com DataAdapters

Aplicável a: .NET Framework .NET .NET Standard

Baixar ADO.NET

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

Quando você chama o método Update, 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 em um DataRow, ele usa InsertCommand, UpdateCommand ou DeleteCommand para processar a alteração.

Essas propriedades permitem maximizar o desempenho do aplicativo ADO.NET, especificando a sintaxe do comando em tempo de design e, sempre que possível, por meio 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, nenhum DeleteCommand para linhas excluídas), será gerada uma exceção.

Importante

Se você estiver usando procedimentos armazenados do SQL Server para editar ou excluir dados usando um DataAdapter, jamais utilize SET NOCOUNT ON na definição do procedimento armazenado. Isso faz com que a contagem retornada de linhas afetadas seja zero, o que o DataAdapter interpreta como um conflito de simultaneidade. Nesse caso, será gerada uma DBConcurrencyException.

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 DataSet. Para obter mais informações, confira Parâmetros do DataAdapter.

Observação

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

Se seu DataTable mapear para ou for gerado a partir de uma única tabela do banco de dados, você poderá aproveitar o objeto DbCommandBuilder para gerar automaticamente os objetos DeleteCommand, InsertCommand e UpdateCommand para o DataAdapter. Para obter mais informações, confira Gerar comandos com CommandBuilders.

Usar o UpdatedRowSource a fim de mapear valores para um DataSet

Você pode controlar como os valores retornados da fonte de dados são mapeados de volta para DataTable após uma chamada ao método Update de DataAdapter, usando a propriedade UpdatedRowSource de um objeto SqlCommand. Definindo a propriedade UpdatedRowSource para um dos valores de enumeração de UpdateRowSource, você pode controlar se os parâmetros de saída retornados pelos comandos de DataAdapter serã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 enumeração de UpdateRowSource e como eles afetam o comportamento de um comando usado com o DataAdapter.

Enumeração de UpdatedRowSource Descrição
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 da 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 método Update resolve as 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 seu DataSet com dados atuais, use o método DataAdapter e o método Fill. As novas linhas serão adicionadas à tabela, e informações atualizadas serão incorporadas às linhas existentes.

O método Fill determina se uma nova linha será adicionada ou se uma linha existente será atualizada examinando os valores de chave primária das linhas no DataSet e as linhas retornadas pelo SelectCommand. Se o método Fill encontrar um valor de chave primária para uma linha no DataSet que corresponda a um valor de chave primária de uma linha nos resultados retornados pelo SelectCommand, ele atualizará a linha existente com as informações da linha retornadas pelo SelectCommand e definirá 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 alguns dos valores de chave primária das linhas do DataSet, o método Fill adicionará uma nova linha com um RowState de Unchanged.

Observação

Se SelectCommand retornar os resultados de um OUTER JOIN, DataAdapter não definirá um valor PrimaryKey para o DataTable resultante. Você deve definir o PrimaryKey você mesmo para garantir que as linhas duplicadas sejam resolvidas corretamente.

Para gerenciar exceções que possam ocorrer ao chamar o método Update, você pode usar o evento RowUpdated para responder a erros na atualização da linha conforme ocorrerem (confira Gerenciar eventos do DataAdapter) ou você pode definir ContinueUpdateOnError como true antes de chamar Update e responder às informações de erro armazenadas na propriedade RowError de uma linha específica quando a atualização for concluída.

Observação

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

Os exemplos a seguir demonstram como executar atualizações em linhas modificadas definindo explicitamente UpdateCommand de DataAdapter e chamando o método Update.

Observação

O parâmetro especificado em WHERE clause de UPDATE statement é definido para usar o valor Original de SourceColumn. Isso é importante, porque o valor Current pode ter sido modificado e não corresponder ao valor na fonte de dados. O valor Original é o valor que foi usado para popular o DataTable da fonte de dados.

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

Colunas AutoIncrement

Se as tabelas de sua fonte de dados possuírem colunas de incremento automático, você poderá preencher as colunas em seu DataSet retornando o valor de incremento automático como um parâmetro de saída de um procedimento armazenado e mapeando esse valor 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 evento RowUpdated do DataAdapter para executar uma instrução SELECT adicional. Para obter mais informações e um exemplo, confira Recuperar valores de identidade ou 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 no DataSet são enviadas para a fonte de dados é importante. Por exemplo, se um valor de chave primária de uma linha existente for atualizado, e uma nova linha for 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 método Select do DataTable para retornar uma matriz de DataRow que referencie somente linhas com um RowState específico. Você pode passar a matriz de DataRow retornada para o método Update do DataAdapter para processar as linhas modificadas. Especificando 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.

Exemplo

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

// Assumes that dataSet and adapter are valid objects.
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 os dados

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

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

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

  • O exemplo usa DataAdapter.FillLoadOption para determinar como o adaptador preenche a DataTable por meio do DbDataReader. Ao criar uma DataTable, você só pode gravar os dados do banco de dado na versão atual ou na versão original, definindo a propriedade como o 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ê precisará 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 Microsoft.Data.SqlClient;
using System.Linq;
using CSDataAdapterOperations.Properties;

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

namespace CSDataAdapterOperations.Properties
{
    internal sealed partial class Settings : System.Configuration.ApplicationSettingsBase
    {
        private static readonly Settings defaultInstance =
            ((Settings)(System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));

        public static Settings Default => defaultInstance;

        [System.Configuration.ApplicationScopedSetting()]
        [System.Configuration.DefaultSettingValue("Data Source=(local);Initial Catalog=MySchool;Integrated Security=True")]
        public string MySchoolConnectionString => ((string)(this["MySchoolConnectionString"]));
    }
}

Confira também