Compartilhar via


Salvar dados de volta no banco de dados em aplicativos do .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 são desconectados do banco de dados. As tecnologias são especialmente úteis para aplicativos que permitem que os usuários modifiquem dados e persistam as alterações no 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 salvar essas alterações no banco de dados. Você faz isso de uma das três maneiras:

  • Chamando um dos métodos Update de um 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ê associa tabelas de conjuntos de dados a controles em um Formulário do Windows ou página XAML, a arquitetura de associação de dados faz todo o trabalho para você.

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

Tópico Descrição
Inserir novos registros em um banco de dados Como executar atualizações e inserções usando objetos TableAdapters ou 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 do Windows Forms para demonstrar como salvar 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

Atualizar 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 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 associados a dados, será necessário 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 atualizar várias fontes de dados. Se você não estiver usando associação de dados e estiver salvando alterações para tabelas relacionadas, precisará criar manualmente uma instância de uma variável da classe gerada automaticamente TableAdapterManager e, em seguida, chamar seu método UpdateAll.

Diagrama conceitual de atualizações de conjunto de dados

Um conjunto de dados contém coleções de tabelas, que contêm uma coleção de linhas. Se você pretende atualizar uma fonte de dados subjacente mais tarde, deverá usar os métodos na DataTable.DataRowCollection propriedade 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 coleção RemoveAt na propriedade Rows, a exclusão não será comunicada 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 (conhecido 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 você precisa integrar dados de vários conjuntos de dados.

Ao mesclar conjuntos de dados, você pode passar um argumento booliano (preserveChanges) que informa ao Merge método se deseja manter as modificações existentes 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 James C. Wilson
Atual Jim Wilson James C. Wilson

Chamar o método Merge na tabela anterior com o argumento preserveChanges=false targetDataset.Merge(sourceDataset) produz os seguintes dados:

DataRowVersion Conjunto de dados de destino Conjunto de dados de origem
Original James C. Wilson James C. Wilson
Atual James C. Wilson James 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 James C. Wilson James C. Wilson
Atual Jim Wilson James C. Wilson

Cuidado

preserveChanges = true Se o método RejectChanges for chamado em um registro no conjunto de dados de destino, ele reverterá aos 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 consiga localizar 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 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 é preenchido.)

Restrições de atualização

Para fazer alterações em uma linha de dados existente, adicionar ou atualizar 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 ao atualizá-lo. 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 de restrição prematuras, você pode suspender temporariamente as restrições de atualização. Isso serve a duas finalidades:

  • Ele impede que um erro seja gerado depois que você terminar de atualizar uma coluna, mas ainda não começou 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 associação de dados incorporada ao datagrid suspende a verificação de restrição até que o foco saia de uma linha e você não precisa chamar explicitamente os métodos BeginEdit, EndEdit ou CancelEdit.

As restrições são desabilitadas automaticamente quando o Merge método é invocado em um conjunto de dados. Quando a mesclagem for concluída, se houver restrições no conjunto de dados que não possam ser habilitadas, uma ConstraintException será gerada. Nessa situação, a EnforceConstraints propriedade é definida 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ê poderá reabilitar a verificação de restrição, que também reabilita e gera os eventos de atualização.

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 têm algum outro problema de integridade. Ou talvez você tenha 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 foi alterado 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 linha de dados específica.

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

Valor de DataRowState Descrição
Added A linha foi adicionada como um item a um DataRowCollection. (Uma linha nesse estado não tem uma versão original correspondente, pois não existia quando o último AcceptChanges método foi chamado).
Deleted A linha foi excluída usando o Delete de um objeto DataRow.
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 ser adicionado a uma coleção e depois de ter sido removido de uma coleção.
Modified Um valor de coluna na linha foi alterado de alguma forma.
Unchanged A linha não foi alterada desde a última vez que AcceptChanges foi chamada.

Enumeração DataRowVersion

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

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

Valor de DataRowVersion 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 haverá nenhuma versão atual.
Default O valor padrão de um registro, conforme definido pelo esquema ou fonte de dados do conjunto de dados.
Original A versão original de um registro é uma cópia do registro, pois foi a última vez que as alterações foram confirmadas no conjunto de dados. Em termos práticos, essa normalmente é a versão de um registro como lida de uma fonte de dados.
Proposed A versão proposta de um registro 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 inverte as alterações e exclui a versão proposta da linha de dados.

As versões originais e atuais 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 do 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 acrescentado à fonte de dados, resultando não apenas em um registro indesejado extra, 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á nenhuma 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 ao solicitar o valor de uma coluna.

Obter registros 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 do 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 apenas 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 desejado DataRowState como um 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 todo o conjunto de dados, você pode reduzir a sobrecarga de comunicação com o outro componente obtendo apenas os registros de que o componente precisa.

Confirmar alterações no conjunto de dados

À medida que as alterações são feitas no conjunto de dados, a RowState propriedade de linhas alteradas é definida. As versões originais e atuais dos registros são estabelecidas, mantidas e disponibilizadas para você pela propriedade RowVersion. 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, você não precisará mais manter essas informações. Normalmente, há duas vezes em que o conjunto de dados e sua origem estão em sincronia:

  • Imediatamente depois de ter carregado informações no conjunto de dados, como quando você lê dados a partir 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 horários:

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

    Observação

    Você pode impedir que o adaptador confirme automaticamente as alterações ao 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 no conjunto de dados para outro processo, como um serviço web XML.

    Cuidado

    Confirmar a alteração dessa forma apaga qualquer informação de alteração. Não confirme as alterações até concluir as operações que exigem que seu aplicativo saiba quais alterações foram feitas no conjunto de dados.

Esse método realiza o seguinte:

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

A tabela a seguir descreve quais alterações 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 confirmadas somente na linha específica.
System.Data.DataTable.AcceptChanges As alterações são confirmadas em todas as linhas da tabela específica.
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 você carregar um conjunto de dados chamando um método tableAdapter Fill , não precisará 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, RejectChangesdesfaz 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 como Unchanged.

Validação de dados

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

Você pode validar dados de várias maneiras:

  • Na camada de negócios, adicionando código ao 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 sendo alterados. 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 do usuário no Windows Forms.

  • No back-end de dados, enviando os dados para a fonte de dados, por exemplo, o banco de dados, e permitindo que 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, isso pode ser uma abordagem prática, pois você pode validar os dados, independentemente de onde eles venham. 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 dados pode resultar em várias viagens de ida e volta à 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á-la 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á transmitir as alterações 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 faz loops em cada registro em uma tabela de dados, determina qual tipo de atualização é necessário (atualizar, inserir ou excluir), se houver, e, em seguida, 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 terá a seguinte 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 "Preferencial". Como resultado dessa alteração, o valor da RowState propriedade dessa linha muda de Unchanged para Modified. O valor da RowState propriedade da 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 sua 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 do 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 compatível com o armazenamento de dados subjacente. No entanto, as seguintes características gerais da instrução SQL transmitida são notáveis:

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

  • A instrução SQL transmitida inclui uma cláusula WHERE que indica que o destino da instrução UPDATE é a linha onde CustomerID = 'c400'. Essa parte da SELECT instrução distingue a linha de destino de todas as outras porque a CustomerID é a chave primária da tabela de destino. As informações da 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 tableAdapter UpdateCommand 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.

Passar parâmetros

Normalmente, você usa parâmetros para passar os valores para registros que serão atualizados no banco de dados. Quando o método tableAdapter Update executa uma instrução UPDATE , ele precisa preencher os valores de parâmetro. Ele obtém esses valores da Parameters coleção para o comando de dados apropriado – nesse caso, o UpdateCommand objeto no TableAdapter.

Se você tiver usado as ferramentas do Visual Studio para gerar um adaptador de dados, o UpdateCommand objeto conterá uma coleção de parâmetros que correspondem a cada espaço reservado de 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 como qualquer coluna na tabela de dados que contenha a ID do autor. Quando o método Update do adaptador é executado, ele lê a coluna da ID do autor do registro que está sendo atualizado e preenche os valores na instrução.

Em uma instrução UPDATE , você precisa especificar os novos valores (aqueles que serão gravados no registro), bem como os valores antigos (para que o registro possa ser localizado no banco de dados). Há, 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 da SET cláusula obtém a versão atual e o parâmetro da 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, o que normalmente faria em um manipulador de eventos para o evento RowChanging do adaptador de dados.