Partilhar via


Atualizar fontes de dados com DataAdapters

Aplica-se a: .NET Framework .NET .NET Standard

Baixar ADO.NET

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

Quando chama o Update método, analisa DataAdapter as alterações feitas e executa o comando apropriado (INSERT, UPDATE ou DELETE). Quando o DataAdapter encontra uma alteração num DataRow, usa o InsertCommand, UpdateCommand, ou DeleteCommand para processar a mudança.

Estas propriedades permitem-lhe maximizar o desempenho da sua aplicação ADO.NET especificando a sintaxe dos comandos no momento do design e, sempre que possível, através do uso de procedimentos armazenados. Deve definir explicitamente os comandos antes de chamar Update. Se Update é chamado e o comando apropriado não existir em uma atualização específica (por exemplo, não existe DeleteCommand para linhas eliminadas), é lançada uma exceção.

Importante

Se estiver a usar procedimentos armazenados no SQL Server para editar ou eliminar dados usando um DataAdapter, certifique-se de que não utiliza SET NOCOUNT ON na definição do 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.

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 num DataSet. Para mais informações, consulte parâmetros DataAdapter.

Observação

É importante perceber a diferença entre apagar uma linha em a DataTable e removê-la. Quando chama o Remove método ou RemoveAt , a linha é removida imediatamente. Quaisquer linhas correspondentes na fonte de dados do back-end não serão afetadas se depois passar o DataTable ou DataSet para um DataAdapter e chamar Update. Quando usa o Delete método, a linha permanece no DataTable e é marcada para eliminação. Se depois passar o DataTable ou DataSet para um DataAdapter e chamar Update, a linha correspondente na fonte de dados do back end é eliminada.

Se o seu DataTable se mapear para ou for gerado a partir de uma única tabela de base de dados, pode tirar partido do objeto DbCommandBuilder para gerar automaticamente o DeleteCommand, InsertCommand e UpdateCommand objetos para o DataAdapter. Para mais informações, consulte Geração de comandos com CommandBuilders.

Utilize o UpdatedRowSource para mapear valores para um DataSet

Pode controlar como os valores devolvidos pela fonte de dados são mapeados de volta para o DataTable após uma chamada ao método Update de um DataAdapter, utilizando a propriedade UpdatedRowSource de um objeto SqlCommand. Ao definir a UpdatedRowSource propriedade para um dos UpdateRowSource valores de enumeração, pode controlar se os parâmetros de saída devolvidos pelos DataAdapter comandos são ignorados ou aplicados à linha alterada no DataSet. Também pode especificar se a primeira linha devolvida (se existir) é aplicada à linha alterada no DataTable.

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

Enumeração UpdatedRowSource Description
Both Tanto os parâmetros de saída como a primeira linha de um conjunto de resultados devolvidos podem ser mapeados para a linha alterada no DataSet.
FirstReturnedRecord Apenas os dados da primeira linha de um conjunto de resultados devolvidos podem ser mapeados para a linha alterada no DataSet.
None Quaisquer parâmetros de saída ou linhas de um conjunto de resultados devolvidos são ignorados.
OutputParameters Apenas os parâmetros de saída podem ser mapeados para a linha alterada no DataSet.

O Update método resolve as suas alterações de volta à fonte de dados; no entanto, outros clientes podem ter modificado os dados na fonte desde a última vez que preencheu o DataSet. Para atualizar o seu DataSet com dados atuais, utilize o método DataAdapterFill. Novas linhas serão adicionadas à tabela e informações atualizadas serão incorporadas nas linhas existentes.

O método Fill determina se uma nova linha será adicionada ou se uma linha existente será atualizada, examinando os valores da chave primária das linhas em DataSet e nas linhas devolvidas pelo SelectCommand. Se o Fill método encontrar um valor de chave primária para uma linha em que DataSet corresponde a um valor de chave primária de uma linha nos resultados devolvidos pelo SelectCommand, atualiza a linha existente com a informação da linha devolvida por o SelectCommand e define o RowState da linha existente para Unchanged. Se uma linha devolvida pelo SelectCommand tiver um valor de chave primária que não corresponda a nenhum dos valores de chave primária das linhas do DataSet, o método Fill adiciona uma nova linha com um RowState de Unchanged.

Observação

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

Para lidar com exceções que possam ocorrer ao chamar o Update método, pode usar o RowUpdated evento para responder a erros de atualização de linha à medida que ocorrem (ver Lidar com eventos DataAdaptador), ou pode definir ContinueUpdateOnError para true antes de chamar Update, e responder à informação de erro armazenada na RowError propriedade de uma determinada linha quando a atualização estiver concluída.

Observação

Chamar AcceptChanges no DataSet, DataTable, ou DataRow fará com que todos os valores de Original para um DataRow sejam sobrescritos pelos valores de Current para o DataRow. Se os valores do campo que identificam a linha como única forem modificados, após chamar AcceptChanges os Original valores deixarão de corresponder aos valores na fonte de dados. AcceptChanges é chamado automaticamente para cada linha durante uma chamada ao método Update de um DataAdapter. Pode preservar os valores originais durante uma chamada ao método Update definindo primeiro a propriedade AcceptChangesDuringUpdate do DataAdapter para false, ou criando um evento para o gestor de eventos RowUpdated e definindo o Status para SkipCurrentRow. Para mais informações, consulte Eventos do DataAdapter.

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

Observação

O parâmetro especificado no WHERE clause do UPDATE statement está definido para usar o valor Original do SourceColumn. Isto é 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 a partir 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 da sua fonte de dados tiverem colunas com auto-incremento, pode preencher as colunas do seu DataSet de várias formas: retornando o valor de auto-incremento como parâmetro de saída de um procedimento armazenado e mapeando-o para uma coluna numa tabela, retornando o valor de auto-incremento na primeira linha de um conjunto de resultados devolvido por um procedimento armazenado ou uma instrução SQL, ou usando o evento RowUpdated de DataAdapter para executar uma instrução SELECT adicional. Para mais informações e um exemplo, consulte Recuperar valores de identidade ou autonumeração.

Ordenação das inserções, atualizações e eliminação

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 foi adicionada com o novo valor da chave primária como chave estrangeira, é importante processar a atualização antes da inserção.

Pode usar o método do DataTableSelect para devolver uma matriz DataRow que apenas faz referência a linhas com um determinado RowState. Pode então passar o array devolvido DataRow para o método Update de DataAdapter processar as linhas modificadas. Ao especificar um subconjunto de linhas a atualizar, pode controlar a ordem em que as inserções, atualizações e eliminações são processadas.

Example

Por exemplo, o código seguinte garante que as linhas eliminadas da tabela são processadas primeiro, depois as linhas atualizadas e, por fim, 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));

Use um DataAdapter para recuperar e atualizar dados

Podes usar um DataAdapter para recuperar e atualizar os dados.

  • A amostra usa DataAdapter.AcceptChangesDuringFill para clonar os dados na base de dados. Se a propriedade for definida como falsa, AcceptChanges não é chamado ao preencher a tabela, e as linhas recém-adicionadas são tratadas como linhas inseridas. Assim, a amostra usa estas linhas para inserir as novas linhas na base de dados.

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

  • O exemplo serve DataAdapter.FillLoadOption para determinar como o adaptador preenche a DataTable a partir do DbDataReader. Quando crias uma DataTable, só podes escrever os dados da base de dados para a versão atual ou original, definindo a propriedade como LoadOption.Upsert ou LoadOption.PreserveChanges.

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

Antes de compilar e executar a amostra, precisa de criar a base 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"]));
    }
}

Consulte também