VB.NET - Tratando a concorrência de dados


Em meu artigo ADO.NET - A arquitetura de dados desconectada e a concorrência de dados lancei as bases conceituais sobre a concorrência de dados na arquitetura desconectada usada pelo ADO.NET. Veremos agora como tratar o problema da concorrência em um exemplo prático.  Este exemplo foi totalmente adaptado do original na MSDN - Walkthrough: Handling a Concurrency Exception .

Neste exemplo vamos criar uma aplicação Windows que dispara um erro de concorrência e ilustra uma das estratégias para o seu tratamento. Vamos simular a utilização por um mesmo sistema por dois usuários que estarão trabalhando com os mesmos dados ao mesmo tempo.

O formulário Windows que iremos criar vai permitir que atuemos como qualquer um dos dois usuários em um único formulário. Basicamente iremos demonstrar as seguintes operações:

  1. Usuário 1 e Usuário 2 irão preencher cada um os seus respectivos datasets com os mesmos dados
  2. O Usuário 2 irá editar um registro , atualizar o dataset , e escrever as alterações na fonte de dados
  3. O Usuário 1 irá editar o mesmo registro, atualizar o dataset e tentar escrever as alterações na fonte de dados; isto irá resultar em um erro de concorrência que irá lançar uma exceção
  4. Iremos então capturar o erro , exibir as diferentes versões do registro e permitir ao usuário determinar qual será a ação a ser tomada com as alterações pendentes feitas pelo Usuário 1.

Para este exemplo vamos usar o banco de dados Pubs do SQL Server 2000 que possui permissão para efetuar alterações e atualizações.

Criando o projeto e a conexão de dados

Crie um novo projeto no VS.NET do tipo Windows Application usando a linguagem VB.NET e dê um nome sugestivo ao projeto; para este exemplo salvei o projeto como concorrenciaDB.

A seguir crie uma nova conexão de dados usando o Server Explorer (se ele não estiver visível exiba-o selecionando no menu View a opção Server Explorer) da seguinte forma:

Retorne ao Server Explorer e expanda a conexão que foi criada ;  expanda  o nó Tables , a seguir selecione a tabela Authors e arreste-a e solte-a no formulário do projeto.

Será criado e exibido na área de componentes do formulário um objeto SqlConnection e um objeto SqlDataAdapter.

Criando os DataSets

Vamos agora criar dois DataSets chamados DsAuthors1 e DsAuthors2 que irão representar os dados com os quais os usuários irão trabalhar. A seguir vamos incluir dois controles DataGrid no formulário e vincular cada um aos respectivos DataSets.

Vamos incluir também dois botões de comando no formulário : um para efetuar a atualização que irá escrever as alterações no banco de dados e outro para resetar, ou seja,  que irá desfazer as alterações feitas no banco de dados.

Você deverá ver na guia de componentes do formulário os seguintes objetos criados:

Fazendo a vinculação de dados

Insira agora dois componentes DataGrids no formulário ; um a esquerda e outro a direita a partir da ToolBox arrastando cada DataGrid para o formulário.

Feito isto vincule cada dataset aos datagrids do formulário seguindo a seguinte orientação:

  1. Selecione o DataGrid1 e defina as seguintes propriedades na janela de Propriedades do controle:
    Propriedade Configuração
    DataSource DsAuthors1
    DataMember authors
    CaptionText DsAuthors1
  2. Seleccione o DataGrid2 defina as seguintes propriedades na janela de Propriedades do controle:
    Propriedade Configuração
    DataSource DsAuthors2
    DataMember authors
    CaptionText DsAuthors2

Agora inclua dois botões de comando no formulário ; um sobre o DataGrid1 com o texto : Atualizar(btnAtualizar) , e outro sobre o DataGrid2 com o texto Resetar(btnResetar).

Ao final o layout obtido deverá ser parecido com o da figura abaixo:

Criando o código do projeto

Clique com o botão direito sobre o formulário e selecione a opção View Code. A seguir inclua as seguintes rotina no formulário:

1- resetaDataBase : desfaz as alterações feitas no banco de dados pelo Usuário 1

 Private Sub resetaDatabase()
        ' preenche o dataset DsAuthors1
        SqlDataAdapter1.Fill(DsAuthors1)
        ' Reseta o campo au_fname na primeira linha para 'John'.
        DsAuthors1.authors(0).au_fname = "John"
        ' Escreve o registro de volta ao banco de dados
        SqlDataAdapter1.Update(DsAuthors1)
    End Sub

 

2- preencheDataSets :  preenche cada dataset com os dados da tabela Authors.

 Private Sub preencheDataSets()
    SqlDataAdapter1.Fill(DsAuthors1)
    SqlDataAdapter1.Fill(DsAuthors2)
End Sub

3-  usuario2_Changes : simula as alterações feitas pelo Usuario 2

  Private Sub usuario2_changes()
        ' Simula a alteração do registro pelo usuario 2
        DsAuthors2.authors(0).au_fname = "Usuario 2"
        ' escreve no banco de dados
        SqlDataAdapter1.Update(DsAuthors2.GetChanges())
        ' refresca DsAuthors2 com os dados atualizados
        SqlDataAdapter1.Fill(DsAuthors2)
    End Sub

4 - No evento Load do formulário insira o seguinte código :

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load, btnResetar.Click
'chama as 3 rotinas criadas
    resetaDatabase()
    preencheDataSets()
    usuario2_changes()
End Sub

Vamos rodar a aplicação e ver o resultado na figura abaixo. Temos cada DataGrid exibindo os dados da tabela Authors do banco de dados Pubs , sendo que o usuário dois alterou o campo au_fname para Usuario 2.

Vamos atualizar o DataSet e escrever as alterações no Banco de dados. Para isto vamos incluir o código que vai tentar realizar a operação de atualizar o banco de dados com as alterações feitas no DataSet DsAuthors1. Se der certo , o método AcceptChanges do dataset será chamado e uma mensagem de sucesso será exibida. Se a atualização falhar , um erro será capturado e uma mensagem de erro será exibida como tipo de objeto como titulo da caixa de mensagem.

Crie então um tratamento de exceção que exibe uma mensagem de aviso  chamada atualizaDataBase conforme o código abaixo:

Private Sub atualizaDatabase()


Try

  ' atualiza o banco de dados com as alterações de DsAuthors1.

   SqlDataAdapter1.Update(DsAuthors1.GetChanges)

   DsAuthors1.AcceptChanges()

     MessageBox.Show("Atualização feita com sucesso...")

Catch ex As Exception

    ' exibe informação sobre erros na atualização

   MessageBox.Show("Atualização falhou ...", ex.GetType.ToString)

End Try


End
Sub

A seguir iremos incluir o código que altera o nome do campo au_fname no dataset DsAuthors1. Este código irá chamar a rotina atualizaDataBase para tentar escrever a alteração no banco de dados. Como o valor foi recentemente alterado pelo Usuário 2 será lançada uma exceção.

No evento click do botão de comando Atualizar inclua o seguinte código:

Private Sub btnAtualizar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAtualizar.Click

  ' Altera o campo au_fname na primeira linha de DsAuthors1
  DsAuthors1.authors(0).au_fname = "Usuario 1"
  atualizaDatabase()

End Sub

Vamos testar ?

Rode o projeto e em seguida clique no botão Atualizar. Uma mensagem de erro , devido a concorrência de dados , deverá ser exibida conforme a figura abaixo. 

Tratando a concorrência de dados

O tratamento da concorrência vai depender da política que você adotou na sua aplicação. Para este exemplo vamos usar a seguinte estratégia:

1- A aplicação irá capturar o erro e irá apresentar ao usuário as 3 situações do registro que esta sendo tratado:

O usuário poderá então ou sobrescrever o banco de dados com as alterações propostas ou cancelar a atualização.

Vamos então criar a rotina que faz a captura da exceção e que exibe as opções ao usuário. Para isto vamos incluir uma captura para o erro específico DBConcurrencyException na rotina  atualizaDataBase que já foi criada. O código da rotina deverá ser alterado para :

Private Sub atualizaDatabase()


Try

  ' atualiza o banco de dados com as alterações de DsAuthors1.

   SqlDataAdapter1.Update(DsAuthors1.GetChanges)

   DsAuthors1.AcceptChanges()

     MessageBox.Show("Atualização feita com sucesso...")

Catch dbcx As DBConcurrencyException                                                                                    

    criaMensagem(dbcx)                                                                                                                  

Catch ex As Exception

    ' exibe informação sobre erros na atualização

   MessageBox.Show("Atualização falhou ...", ex.GetType.ToString)

End Try


End
Sub


Precisamos criar agora a rotina criaMensagem() que irá exibir as opções ao usuário.

 

Private Sub criaMensagem(ByVal dbcx As DBConcurrencyException)


' Declara variáveis para tratar as verões do registro na mensagem variables to hold the row versions for display

Dim strInDs As String = "registro original em DsAuthors1:" & ControlChars.CrLf

Dim strInDB As String = "Registro atual no banco de dados:" & ControlChars.CrLf

Dim strProposed As String = "Alteração proposta:" & ControlChars.CrLf

Dim strPromptText As String = "Você deseja sobrescrever o registro atual no banco de dados a alteração proposta ? " & ControlChars.CrLf

Dim strMessage As String

Dim response As System.Windows.Forms.DialogResult


' Localiza o registro atual na fonte de dados que causou a exceção

Dim rowInDB As DataRow = DsAuthors2.authors.FindByau_id(CType(dbcx.Row("au_id"), String))


' percorre os valores da coluna

Dim i As Integer


For
i = 0 To
dbcx.Row.ItemArray.Length - 1

   strInDs &= dbcx.Row(i, DataRowVersion.Original) & ControlChars.Tab

   strInDB &= rowInDB(i, DataRowVersion.Current) & ControlChars.Tab

   strProposed &= dbcx.Row(i, DataRowVersion.Current) & ControlChars.Tab

Next
 

' cria a mensagem

strMessage = strInDs & ControlChars.CrLf

strMessage &= strInDB & ControlChars.CrLf

strMessage &= strProposed & ControlChars.CrLf

strMessage &= strPromptText


' exibe a mensagem

resposta = MessageBox.Show(strMessage, "Atualização Falhou...", MessageBoxButtons.YesNo)

 

processaResposta(resposta)


End
Sub

 

 

Teremos que criar também uma rotina que irá processar a resposta do usuário, a rotina processaResposta().

 

Se o usuário escolher sobrescrever o registro atual no banco de dados com as alterações proposta , o método Merge do DsAuthors1 será chamado com o argumento preserveChanges definido como True. Com isto estamos tomando a versão original da linha de dados em DsAuthors2 e mesclando-a com a versão atual em DsAuthors1. Isto implicará em que a tentativa de atualização será bem sucedida pois a

versão original do registro coincide com a do banco de dados.

 

Private Sub processaResposta(ByVal response As System.Windows.Forms.DialogResult)
 

Select Case response

    Case System.Windows.Forms.DialogResult.Yes

          ' Sobrescreve o banco de dados com as alterações propostas

         DsAuthors1.Merge(DsAuthors2, True)

         SqlDataAdapter1.Update(DsAuthors1)

         DsAuthors1.AcceptChanges()

        Case System.Windows.Forms.DialogResult.No

              ' Cancela as alterações propostas

          DsAuthors1.Merge(DsAuthors2)

End Select
 

End Sub

Resetando os  dados

Vamos agora tratar do código do botão - Resetar.  Para isto teremos que incluir o tratamento do evento Click do botão Resetar no evento Load do formulário.

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load, btnResetar.Click

Finalmente podemos ver o resultado. Rode a aplicação e clique no botão atualizar. Uma exceção de concorrência será lançada e tratada exibindo a mensagem ao usuário conforme figura abaixo:

Com isto encerramos o nosso exemplo completo que mostra uma das possibilidades de como tratar a concorrência de dados.

Eu sei é apenas VB.NET , mas eu gosto...


José Carlos Macoratti