conheça silentidea e aproveite melhor suas horas diante do computador

Visual Basic, VB .NET, ASP, Active X, Access, SQL Server

Viva Sem Bloquear Registros


      Num ambiente multi-usuários o programador tem sempre que se preocupar com o problema do uso simultâneo das mesmas informações por vários usuários. O que muitas vezes se faz é recorrer ao bloqueio de registros, que pode ser feito só no momento da alteração do registro ou tão logo o usuário o acesse para iniciar uma atualização. Se partirmos para a segunda forma de bloqueio, vamos segurar um registro bloqueado por quanto tempo? E se o usuário que o está bloqueando sair para almoçar ou tomar um café? Seja qual for a forma de bloqueio nenhuma delas é boa. O bloqueio otimista, que só se dá no momento da atualização, não nos impede de acessar um registro, exibir suas informações na tela e, baseado nestas informações, realizar uma alteração que pode estar em total discordância com o estado atual do registro no momento em que o atualizamos. Enquanto exibimos os dados, um outro usuário pode acessar o mesmo registro e fazer uma alteração que cria uma condição completamente nova na base de dados. Além de desconhecermos esta alteração (o que poderia nos fazer mudar de idéia a respeito da nossa atualização), também poderemos estar enviando dados antigos para a base de dados e desfazendo o que o outro usuário acabou de fazer. Nesta situação estamos diante do que se chama de uma colisão de dados. Como então lidar com o problema da concorrência pelas mesmas informações num ambiente multi-usuários e tratar as colisões de dados? 

Há duas soluções que se pode usar. Uma é usar o conceito de "versão de registro". Uma idéia bem simples e que dá maior liberdade e segurança no acesso às informações. A idéia é ter um identificador de versão para cada registro em cada tabela que será atualizada por múltiplos usuários. A versão pode ser implementada criando-se um campo numérico inteiro no layout  de registro e com valor padrão 0. Toda vez que um registro é acessado para  ser modificado, trazemos com seus dados também o campo que contém a identificação de versão do registro. Após reunirmos os dados para fazer a atualização, criamos uma instrução "UPDATE" da linguagem SQL onde condicionamos na cláusula WHERE que o registro somente será  atualizado se o seu identificador de versão atual for igual àquele que obtivemos no momento que acessamos seus dados. Se esta condição estiver satisfeita, atualizaremos o registro acrescentando 1 ao valor atual do identificador de versão do registro. Exemplo: 

"UPDATE Veiculos SET PLACA = 'AAA1234', CHASSI = '1234567890WERTYU', IdentificadorDeVersao = IdentificadorDeVersao + 1 WHERE CodVeiculo = 1000 AND IdentificadorDeVersao = " & lngUltimoIdentificadorDeVersaoObtido

Desta forma, se um outro usuário estiver visualizando o registro e quiser enviar uma atualização, seu identificador de versão não mais será igual àquele que ele recebeu no momento que leu o registro anteriormente. E qualquer tentativa de atualizar o registro não se cumprirá, porque a condição de estar trabalhando com a versão mais atual do registro não será satisfeita na sua instrução "UPDATE". 

Neste caso então, o programa deve verificar o número de registros afetados pela operação (neste caso será 0) e buscar na base de dados os valores atuais para os campos do registro. Comparará campo a campo os seus dados com os atuais e mostrará uma mensagem para o usuário informando o que mudou e perguntando se deve continuar. Caso o usuário concorde em atualizar o registro, uma nova tentativa de envio dos  dados será feita, mas só que agora informando o identificador de versão mais atual para não haver novamente o conflito. Caso um outro usuário tenha feito uma nova alteração neste intervalo de tempo, o identificador de versão terá sido modificado e  o conflito se repetirá. O mesmo procedimento será seguido até que as versões de registros confiram ou o usuário cancele a atualização. 

O inconveniente sério da abordagem acima é que o seu programa pode não ser o único a acessar a mesma base de dados. E como garantir então que os demais programas seguirão a mesma técnica nas atualizações de registros? Não há como garantir isto. Para simplificar as coisas ainda mais e evitar o inconveniente acima, podemos fazer o mesmo que o ADO faz quando chamamos o método UpdateBatch de um recordset. Quando se chama o método UpdateBatch, o ADO cria uma consulta de ação para cada registro que precisa ser incluído, atualizado ou excluído. Em uma atualização, a consulta de ação é criada usando o critério da cláusula WHERE para comparar os valores de todos os campos do registro que será atualizado com os valores que estes campos tinham no momento que foram lidos. Se houver alguma diferença, é porque outro usuário já atualizou o registro ou o excluiu, e o número de registros afetados pela operação será zero. Verificando o número de registros afetados pela consulta e a não ocorrência de erros, o programa atualizador tem como saber se houve uma colisão de dados. Nestes casos, é feita uma consulta para recuperar o estado atual do registro e retorná-lo para o usuário com as devidas comparações. Isto nos livra de ter que usar um campo em cada tabela para indicar a versão de registros e também nos tira do risco de uma outra aplicação produzir inconsistências no nosso modo de trabalhar. O inconveniente aqui é o de ter de criar consultas tão longas quantos forem os campos da tabela a ser atualizada. Você pode perguntar: E por que não usar logo o método UpdateBatch do recordset ADO? Bem, se você estiver trabalhando em três camadas, irá descobrir o inconveniente de não conseguir retornar o valor da propriedade UnderlyingValue dos campos de um recordset desconectado. Esta propriedade é usada pelo método Resync que você chamaria para tratar colisões de dados após um UpdateBatch. O método Resync, adequadamente chamado, colocaria nesta propriedade os valores atuais dos campos do registro conflitante para que você os comparasse com os valores da propriedade OriginalValue de cada campo. Como os valores de UnderlyingValue não são retornados para o cliente, você teria o trabalho de montar um esquema de retorno destes dados feito à parte. Além disto, o automatismo de UpdateBatch pode dificultar o seu controle sobre a operação, principalmente se você estiver trabalhando com Access. Você pode, por exemplo, querer gravar históricos para as operações que são feitas em cada registro. Vale lembrar também que, para trabalhar com os métodos UpdateBatch e Resync, você precisará ter no recordset os campos chave primária de qualquer tabela fornecedora de campos para a consulta  geradora do recordset. O ADO precisa destas informações para montar suas consultas internamente.


conheça silentidea e aproveite melhor suas horas diante do computador