Partilhar via


Salve dados de volta ao banco de dados em aplicativos .NET Framework

Observação

A DataSet classe e as classes relacionadas são tecnologias herdadas do .NET Framework do início dos anos 2000 que permitem que os aplicativos trabalhem com dados na memória enquanto os aplicativos estão desconectados do banco de dados. As tecnologias são especialmente úteis para aplicativos que permitem aos usuários modificar dados e manter as alterações de volta ao banco de dados. Embora os conjuntos de dados sejam uma tecnologia comprovadamente bem-sucedida, a abordagem recomendada para novos aplicativos .NET é usar o Entity Framework Core. O Entity Framework fornece uma maneira mais natural de trabalhar com dados tabulares como modelos de objeto e tem uma interface de programação mais simples.

O conjunto de dados é uma cópia de dados na memória. Se você modificar esses dados, é uma boa prática salvá-los novamente no banco de dados. Você faz isso de três maneiras:

  • Chamando um dos Update métodos do TableAdapter

  • Chamando um dos DBDirect métodos do TableAdapter

  • Chamando o UpdateAll método no TableAdapterManager que o Visual Studio gera para você quando o conjunto de dados contém tabelas relacionadas a outras tabelas no conjunto de dados

Quando você vincula tabelas de conjunto de dados a controles em uma página Windows Form ou XAML, a arquitetura de vinculação de dados faz todo o trabalho para você.

Se você estiver familiarizado com TableAdapters, você pode ir diretamente para um destes tópicos:

Tópico Descrição
Inserir novos registos numa base de dados Como executar atualizações e inserções usando TableAdapters ou objetos Command
Atualizar dados usando um TableAdapter Como executar atualizações com TableAdapters
Atualização hierárquica Como executar atualizações de um conjunto de dados com duas ou mais tabelas relacionadas
Manipular uma exceção de simultaneidade Como lidar com exceções quando dois usuários tentam alterar os mesmos dados em um banco de dados ao mesmo tempo
Como: Salvar dados usando uma transação Como salvar dados em uma transação usando o Sistema. Namespace Transactions e um objeto TransactionScope
Salvar dados em uma transação Passo a passo que cria um aplicativo Windows Forms para demonstrar o salvamento de dados em um banco de dados dentro de uma transação
Salvar dados em um banco de dados (várias tabelas) Como editar registros e salvar alterações em várias tabelas de volta ao banco de dados
Salvar dados de um objeto em um banco de dados Como passar dados de um objeto que não está em um conjunto de dados para um banco de dados usando um método TableAdapter DbDirect
Salvar dados com os métodos TableAdapter DBDirect Como usar o TableAdapter para enviar consultas SQL diretamente para o banco de dados
Salvar um conjunto de dados como XML Como salvar um conjunto de dados em um documento XML

Atualizações em dois estágios

A atualização de uma fonte de dados é um processo de duas etapas. A primeira etapa é atualizar o conjunto de dados com novos registros, registros alterados ou registros excluídos. Se o seu aplicativo nunca enviar essas alterações de volta para a fonte de dados, você terminará a atualização.

Se você enviar as alterações de volta para o banco de dados, uma segunda etapa será necessária. Se você não estiver usando controles ligados a dados, precisará chamar manualmente o Update método do mesmo TableAdapter (ou adaptador de dados) que você usou para preencher o conjunto de dados. No entanto, você também pode usar adaptadores diferentes, por exemplo, para mover dados de uma fonte de dados para outra ou para atualizar várias fontes de dados. Se você não estiver usando a vinculação de dados e estiver salvando alterações para tabelas relacionadas, será necessário instanciar manualmente uma variável da classe gerada TableAdapterManager automaticamente e, em seguida, chamar seu UpdateAll método.

Diagrama conceitual de atualizações do conjunto de dados

Um conjunto de dados contém coleções de tabelas, que contêm coleções de linhas. Se pretendes atualizar uma fonte de dados subjacente mais tarde, deves usar os métodos da propriedade DataTable.DataRowCollection ao adicionar ou remover linhas. Esses métodos executam o controle de alterações necessário para atualizar a fonte de dados. Se você chamar a RemoveAt coleção na propriedade Rows, a exclusão não será comunicada de volta ao banco de dados.

Mesclar conjuntos de dados

Você pode atualizar o conteúdo de um conjunto de dados mesclando-o com outro conjunto de dados. Isso envolve copiar o conteúdo de um conjunto de dados de origem para o conjunto de dados de chamada (referido como o conjunto de dados de destino ). Quando você mescla conjuntos de dados, novos registros no conjunto de dados de origem são adicionados ao conjunto de dados de destino. Além disso, colunas extras no conjunto de dados de origem são adicionadas ao conjunto de dados de destino. A mesclagem de conjuntos de dados é útil quando você tem um conjunto de dados local e obtém um segundo conjunto de dados de outro aplicativo. Também é útil quando você obtém um segundo conjunto de dados de um componente, como um serviço Web XML, ou quando precisa integrar dados de vários conjuntos de dados.

Ao mesclar conjuntos de dados, você pode passar um argumento booleano (preserveChanges) que informa ao Merge método se as modificações existentes devem ser mantidas no conjunto de dados de destino. Como os conjuntos de dados mantêm várias versões de registros, é importante ter em mente que mais de uma versão dos registros está sendo mesclada. A tabela a seguir mostra como um registro em dois conjuntos de dados é mesclado:

DataRowVersion Conjunto de dados de destino Conjunto de dados de origem
Original James Wilson Tiago C. Wilson
Atual Jim Wilson Tiago C. Wilson

Ao chamar o método Merge na tabela anterior com preserveChanges=false targetDataset.Merge(sourceDataset), obtêm-se os seguintes dados:

DataRowVersion Conjunto de dados de destino Conjunto de dados de origem
Original Tiago C. Wilson Tiago C. Wilson
Atual Tiago C. Wilson Tiago C. Wilson

Chamar o método Merge com preserveChanges = true targetDataset.Merge(sourceDataset, true) resulta nos seguintes dados:

DataRowVersion Conjunto de dados de destino Conjunto de dados de origem
Original Tiago C. Wilson Tiago C. Wilson
Atual Jim Wilson Tiago C. Wilson

Atenção

No cenário preserveChanges = true, se o método RejectChanges for chamado sobre um registo no conjunto de dados de destino, ele reverte para os dados originais do conjunto de dados de origem. Isso significa que, se você tentar atualizar a fonte de dados original com o conjunto de dados de destino, talvez não seja possível encontrar a linha original a ser atualizada. Você pode evitar uma violação de simultaneidade preenchendo outro conjunto de dados com os registros atualizados da fonte de dados e, em seguida, executando uma mesclagem para evitar uma violação de simultaneidade. (Uma violação de simultaneidade ocorre quando outro usuário modifica um registro na fonte de dados depois que o conjunto de dados foi preenchido.)

Restrições de atualização

Para fazer alterações em uma linha de dados existente, adicione ou atualize dados nas colunas individuais. Se o conjunto de dados contiver restrições (como chaves estrangeiras ou restrições não anuláveis), é possível que o registro esteja temporariamente em um estado de erro à medida que você o atualiza. Ou seja, ele pode estar em um estado de erro depois que você terminar de atualizar uma coluna, mas antes de chegar à próxima.

Para evitar violações prematuras de restrições, você pode suspender temporariamente as restrições de atualização. Isto serve dois propósitos:

  • Ele evita que um erro seja gerado depois que você terminar de atualizar uma coluna, mas não tiver começado a atualizar outra.

  • Ele impede que determinados eventos de atualização sejam gerados (eventos que geralmente são usados para validação).

Observação

No Windows Forms, a arquitetura de vinculação de dados incorporada à grade de dados suspende a verificação de restrições até que o foco saia de uma linha, não sendo necessário chamar explicitamente os métodos BeginEdit, EndEdit ou CancelEdit.

As restrições são automaticamente desabilitadas quando o método é invocado Merge em um conjunto de dados. Quando a mesclagem estiver concluída, se houver restrições no conjunto de dados que não possam ser habilitadas, um ConstraintException será lançado. Nessa situação, a EnforceConstraints propriedade é definida como false, e todas as violações de restrição devem ser resolvidas antes de redefinir a EnforceConstraints propriedade para true.

Depois de concluir uma atualização, você pode reativar a verificação de restrições, que também reativa os eventos de atualização e os gera.

Para obter mais informações sobre como suspender eventos, consulte Desativar restrições ao preencher um conjunto de dados.

Erros de atualização do conjunto de dados

Quando você atualiza um registro em um conjunto de dados, há a possibilidade de um erro. Por exemplo, você pode gravar inadvertidamente dados do tipo errado em uma coluna, ou dados muito longos, ou dados que tenham algum outro problema de integridade. Ou, você pode ter verificações de validação específicas do aplicativo que podem gerar erros personalizados durante qualquer estágio de um evento de atualização. Para obter mais informações, consulte Validar dados em conjuntos de dados.

Manter informações sobre alterações

As informações sobre as alterações em um conjunto de dados são mantidas de duas maneiras: sinalizando linhas que indicam que elas foram alteradas (RowState), e mantendo várias cópias de um registro (DataRowVersion). Usando essas informações, os processos podem determinar o que mudou no conjunto de dados e podem enviar atualizações apropriadas para a fonte de dados.

Propriedade RowState

A RowState propriedade de um DataRow objeto é um valor que fornece informações sobre o status de uma determinada linha de dados.

A tabela a seguir detalha os valores possíveis da DataRowState enumeração:

Valor DataRowState Descrição
Added A linha foi adicionada como um item a um DataRowCollection. (Uma linha neste estado não tem uma versão original correspondente, uma vez que não existia quando o último AcceptChanges método foi chamado).
Deleted A linha foi excluída usando o Delete de um DataRow objeto.
Detached A linha foi criada, mas não faz parte de nenhum DataRowCollection. Um DataRow objeto está nesse estado imediatamente após ter sido criado, antes de ter sido adicionado a uma coleção e depois de ter sido removido de uma coleção.
Modified O valor de uma coluna na linha foi alterado de alguma forma.
Unchanged A fila não mudou desde que AcceptChanges foi chamada pela última vez.

DataRowVersion enumeração

Os conjuntos de dados mantêm várias versões de registros. Os DataRowVersion campos são usados ao recuperar o valor encontrado em um DataRow usando a Item[] propriedade ou o GetChildRows método do DataRow objeto.

A tabela a seguir detalha os valores possíveis da DataRowVersion enumeração:

DataRowVersion Valor Descrição
Current A versão atual de um registro contém todas as modificações que foram executadas no registro desde a última vez AcceptChanges que foi chamada. Se a linha tiver sido excluída, não há nenhuma versão atual.
Default O valor padrão de um registro, conforme definido pelo esquema do conjunto de dados ou fonte de dados.
Original A versão original de um registro é uma cópia do registro, pois foi a última vez que alterações foram confirmadas no conjunto de dados. Em termos práticos, esta é normalmente a versão de um registo lida a partir de uma fonte de dados.
Proposed A versão proposta de um registo que está disponível temporariamente enquanto você está no meio de uma atualização — ou seja, entre o momento em que você chamou o método BeginEdit e o método EndEdit. Normalmente, você acessa a versão proposta de um registro em um manipulador para um evento como RowChanging. Invocar o CancelEdit método reverte as alterações e exclui a versão proposta da linha de dados.

As versões original e atual são úteis quando as informações de atualização são transmitidas para uma fonte de dados. Normalmente, quando uma atualização é enviada para a fonte de dados, as novas informações para o banco de dados estão na versão atual de um registro. As informações da versão original são usadas para localizar o registro a ser atualizado.

Por exemplo, em um caso em que a chave primária de um registro é alterada, você precisa de uma maneira de localizar o registro correto na fonte de dados para atualizar as alterações. Se nenhuma versão original existisse, o registro provavelmente seria anexado à fonte de dados, resultando não apenas em um registro extra indesejado, mas em um registro impreciso e desatualizado. As duas versões também são usadas no controle de simultaneidade. Você pode comparar a versão original com um registro na fonte de dados para determinar se o registro foi alterado desde que foi carregado no conjunto de dados.

A versão proposta é útil quando você precisa executar a validação antes de realmente confirmar as alterações no conjunto de dados.

Mesmo que os registros tenham sido alterados, nem sempre há versões originais ou atuais dessa linha. Quando você insere uma nova linha na tabela, não há uma versão original, apenas uma versão atual. Da mesma forma, se você excluir uma linha chamando o método da Delete tabela, haverá uma versão original, mas nenhuma versão atual.

Você pode testar para ver se existe uma versão específica de um registro consultando o método de uma linha de HasVersion dados. Você pode acessar qualquer versão de um registro passando um DataRowVersion valor de enumeração como um argumento opcional quando solicitar o valor de uma coluna.

Obter registos alterados

É uma prática comum não atualizar todos os registros em um conjunto de dados. Por exemplo, um usuário pode estar trabalhando com um controle Windows Forms DataGridView que exibe muitos registros. No entanto, o usuário pode atualizar apenas alguns registros, excluir um e inserir um novo. Conjuntos de dados e tabelas de dados fornecem um método (GetChanges) para retornar somente as linhas que foram modificadas.

Você pode criar subconjuntos de registros alterados usando o GetChanges método da tabela de dados (GetChanges) ou do próprio conjunto de dados (GetChanges). Se você chamar o método para a tabela de dados, ele retornará uma cópia da tabela com apenas os registros alterados. Da mesma forma, se você chamar o método no conjunto de dados, obterá um novo conjunto de dados com apenas registros alterados nele.

GetChanges por si só retorna todos os registros alterados. Por outro lado, passando o parâmetro desejado DataRowState como parâmetro para o GetChanges método, você pode especificar qual subconjunto de registros alterados deseja: registros recém-adicionados, registros marcados para exclusão, registros desanexados ou registros modificados.

Obter um subconjunto de registros alterados é útil quando você deseja enviar registros para outro componente para processamento. Em vez de enviar o conjunto de dados inteiro, você pode reduzir a sobrecarga de comunicação com o outro componente obtendo apenas os registros de que o componente precisa.

Aplicar alterações no conjunto de dados

À medida que as alterações são feitas no conjunto de dados, a RowState propriedade das linhas alteradas é definida. As versões original e atual dos registros são estabelecidas, mantidas e disponibilizadas a você pela RowVersion propriedade. Os metadados armazenados nas propriedades dessas linhas alteradas são necessários para enviar as atualizações corretas para a fonte de dados.

Se as alterações refletirem o estado atual da fonte de dados, não será mais necessário manter essas informações. Normalmente, há dois momentos em que o conjunto de dados e sua origem estão sincronizados:

  • Imediatamente após ter carregado informações no conjunto de dados, como quando você lê dados da fonte.

  • Depois de enviar alterações do conjunto de dados para a fonte de dados (mas não antes, porque você perderia as informações de alteração necessárias para enviar alterações ao banco de dados).

Você pode confirmar as alterações pendentes no conjunto de dados chamando o AcceptChanges método. Normalmente, AcceptChanges é chamado nos seguintes momentos:

  • Depois de carregar o conjunto de dados. Se você carregar um conjunto de dados chamando um método Fill de um TableAdapter, o adaptador confirmará automaticamente as alterações para você. No entanto, se você carregar um conjunto de dados mesclando outro conjunto de dados nele, precisará confirmar as alterações manualmente.

    Observação

    Você pode impedir que o adaptador confirme automaticamente as alterações quando você chamar o Fill método definindo a AcceptChangesDuringFill propriedade do adaptador como false. Se estiver definido como false, a RowState de cada linha inserida durante o preenchimento será definida como Added.

  • Depois de enviar alterações ao conjunto de dados para outro processo, como um serviço web XML.

    Atenção

    Confirmar a alteração desta forma apaga qualquer informação de alteração. Não confirme alterações até terminar de executar operações que exijam que seu aplicativo saiba quais alterações foram feitas no conjunto de dados.

Este método realiza o seguinte:

  • Grava a versão Current de um registo na sua versão Original e sobrepõe a versão original.

  • Remove qualquer linha em que a RowState propriedade esteja definida como Deleted.

  • Define a RowState propriedade de um registro como Unchanged.

O AcceptChanges método está disponível em três níveis. Você pode chamá-lo num DataRow objeto para cometer alterações apenas para essa linha. Você também pode chamá-lo em um DataTable objeto para confirmar todas as linhas em uma tabela. Finalmente, você pode chamá-lo no DataSet objeto para confirmar todas as alterações pendentes em todos os registros de todas as tabelas do conjunto de dados.

A tabela a seguir descreve as alterações que são confirmadas com base no objeto em que o método é chamado.

Método Resultado
System.Data.DataRow.AcceptChanges As alterações são aplicadas apenas na linha específica.
System.Data.DataTable.AcceptChanges As alterações são validadas em todas as linhas da tabela em questão.
System.Data.DataSet.AcceptChanges As alterações são confirmadas em todas as linhas em todas as tabelas do conjunto de dados.

Observação

Se carregar um conjunto de dados através do método Fill de um TableAdapter, não será necessário aceitar explicitamente as alterações. Por padrão, o Fill método chama o AcceptChanges método depois que ele termina de preencher a tabela de dados.

Um método relacionado, RejectChanges, desfaz o efeito das alterações copiando a Original versão de volta para a Current versão dos registros. Ele também define o RowState de cada registro de volta para Unchanged.

Validação de dados

Para verificar se os dados em seu aplicativo atendem aos requisitos dos processos para os quais são passados, muitas vezes você precisa adicionar validação. Isso pode envolver verificar se a entrada de um usuário em um formulário está correta, validar dados enviados ao seu aplicativo por outro aplicativo ou até mesmo verificar se as informações calculadas dentro do seu componente estão dentro das restrições da fonte de dados e dos requisitos do aplicativo.

Você pode validar dados de várias maneiras:

  • Na camada de negócios, adicionando código ao seu aplicativo para validar dados. O conjunto de dados é um lugar onde você pode fazer isso. O conjunto de dados fornece algumas das vantagens da validação de back-end — como a capacidade de validar alterações à medida que os valores de coluna e linha estão mudando. Para obter mais informações, consulte Validar dados em conjuntos de dados.

  • Na camada de apresentação, adicionando validação aos formulários. Para obter mais informações, consulte Validação de entrada de usuário no Windows Forms.

  • No back-end de dados, enviando dados para a fonte de dados — por exemplo, o banco de dados — e permitindo que ela aceite ou rejeite os dados. Se você estiver trabalhando com um banco de dados que tenha recursos sofisticados para validar dados e fornecer informações de erro, essa pode ser uma abordagem prática porque você pode validar os dados, não importa de onde eles vêm. No entanto, essa abordagem pode não acomodar requisitos de validação específicos do aplicativo. Além disso, fazer com que a fonte de dados valide os dados pode resultar em inúmeras viagens de ida e volta para a fonte de dados, dependendo de como seu aplicativo facilita a resolução de erros de validação gerados pelo back-end.

    Importante

    Ao usar comandos de dados com uma CommandType propriedade definida como Text, verifique cuidadosamente as informações enviadas de um cliente antes de passá-las para o banco de dados. Usuários mal-intencionados podem tentar enviar (injetar) instruções SQL modificadas ou adicionais em um esforço para obter acesso não autorizado ou danificar o banco de dados. Antes de transferir a entrada do usuário para um banco de dados, sempre verifique se as informações são válidas. É uma prática recomendada sempre usar consultas parametrizadas ou procedimentos armazenados quando possível.

Transmitir atualizações para a fonte de dados

Depois que as alterações forem feitas em um conjunto de dados, você poderá transmiti-las para uma fonte de dados. Mais comumente, você faz isso chamando o Update método de um TableAdapter (ou adaptador de dados). O método percorre cada registro em uma tabela de dados, determina que tipo de atualização é necessária (atualizar, inserir ou excluir), se houver, e executa o comando apropriado.

Como ilustração de como as atualizações são feitas, suponha que seu aplicativo use um conjunto de dados que contenha uma única tabela de dados. O aplicativo busca duas linhas do banco de dados. Após a recuperação, a tabela de dados na memória tem esta aparência:

(RowState)     CustomerID   Name             Status
(Unchanged)    c200         Robert Lyon      Good
(Unchanged)    c400         Nancy Buchanan    Pending

Seu aplicativo altera o status de Nancy Buchanan para "Preferido". Como resultado dessa alteração, o RowState valor da propriedade dessa linha muda de Unchanged para Modified. O valor da RowState propriedade para a primeira linha permanece Unchanged. A tabela de dados agora tem esta aparência:

(RowState)     CustomerID   Name             Status
(Unchanged)    c200         Robert Lyon      Good
(Modified)     c400         Nancy Buchanan    Preferred

Seu aplicativo agora chama o Update método para transmitir o conjunto de dados para o banco de dados. O método inspeciona cada linha por vez. Para a primeira linha, o método não transmite nenhuma instrução SQL para o banco de dados porque essa linha não foi alterada desde que foi originalmente buscada no banco de dados.

Para a segunda linha, no entanto, o Update método invoca automaticamente o comando de dados correto e o transmite para o banco de dados. A sintaxe específica da instrução SQL depende do dialeto do SQL suportado pelo armazenamento de dados subjacente. Mas, as seguintes características gerais da instrução SQL transmitida são dignas de nota:

  • A instrução SQL transmitida é uma UPDATE instrução. O adaptador sabe que deve utilizar uma UPDATE declaração porque o valor da propriedade RowState é Modified.

  • A instrução SQL transmitida inclui uma WHERE cláusula que indica que o destino da UPDATE instrução é a linha onde CustomerID = 'c400'. Esta parte da SELECT instrução distingue a linha de destino de todas as outras porque a é a CustomerID chave primária da tabela de destino. As informações para a WHERE cláusula são derivadas da versão original do registro (DataRowVersion.Original), caso os valores necessários para identificar a linha tenham sido alterados.

  • A instrução SQL transmitida inclui a SET cláusula para definir os novos valores das colunas modificadas.

    Observação

    Se a propriedade do UpdateCommand TableAdapter tiver sido definida como o nome de um procedimento armazenado, o adaptador não construirá uma instrução SQL. Em vez disso, ele invoca o procedimento armazenado com os parâmetros apropriados passados.

Transmitir parâmetros

Você geralmente usa parâmetros para passar os valores dos registros que serão atualizados no banco de dados. Quando o método do Update TableAdapter executa uma instrução UPDATE, este precisa preencher os valores de parâmetro. Ele obtém esses valores da Parameters coleção para o comando de dados apropriado — neste caso, o UpdateCommand objeto no TableAdapter.

Se você usou as ferramentas do Visual Studio para gerar um adaptador de dados, o UpdateCommand objeto contém uma coleção de parâmetros que correspondem a cada espaço reservado para parâmetro na instrução.

A System.Data.SqlClient.SqlParameter.SourceColumn propriedade de cada parâmetro aponta para uma coluna na tabela de dados. Por exemplo, a propriedade SourceColumn para os parâmetros au_id e Original_au_id é definida para qualquer coluna na tabela de dados que contenha o id do autor. Quando o método Update do adaptador é executado, ele lê a coluna do id do autor do registo que está a ser atualizado e preenche os valores na instrução.

Em uma UPDATE instrução, você precisa especificar os novos valores (aqueles que serão gravados no registro) e os valores antigos (para que o registro possa ser localizado no banco de dados). Existem, portanto, dois parâmetros para cada valor: um para a SET cláusula e outro para a WHERE cláusula. Ambos os parâmetros leem dados do registro que está sendo atualizado, mas obtêm versões diferentes do valor da coluna com base na propriedade do SourceVersion parâmetro. O parâmetro para a SET cláusula obtém a versão atual e o parâmetro para a WHERE cláusula obtém a versão original.

Observação

Você também pode definir valores na coleção Parameters por conta própria no código, como normalmente faria em um manipulador de eventos para um evento do adaptador de dados RowChanging.