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:
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:
Propriedade | Configuração |
DataSource | DsAuthors1 |
DataMember | authors |
CaptionText | DsAuthors1 |
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()
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 TryEnd 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 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()
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 TryEnd 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 SelectEnd 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.ClickFinalmente 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