ADO.NET - Tratando a concorrência de dados (rowversion)
Como posso prevenir que usuários de meu sistema atualizem dados que outros usuários estão editando ?
Como tratar efetivamente a concorrência de dados em aplicações de acesso a dados ?
Nota: Se tiver dúvidas sobre concorrência leia o artigo: ADO.NET - A arquitetura de dados desconectada e a concorrência de dados.
Geralmente uma das primeiras 'soluções' que passam pela cabeça de um desenvolvedor é que ele deve de alguma forma bloquear a tabela ou as tabelas enquanto um usuário estiver atualizando/editando os dados.
Só tem um problema nesta solução: quando você usa ADO .NET você esta implicitamente usando o paradigma dos dados desconectados e, e isso por si só já torna a solução proposta surrealista e em muitos casos impraticável.
Desta forma em um aplicação como você deve tratar a concorrência de forma que as alterações feitas por outro usuário não sejam sobrepostas ?
Não existe uma receita pronta e única para tratar este problema , existem muitas formas de contorná-lo, e, uma delas é tomar vantagem do tipo de dados embutido no SQL Server , estou me referindo ao TimeStamp ou tipo rowversion. ( Não confunda o tipo de dados nativo do SQL Server com a propriedade DataRowView.RowVersion da ADO.NET)
A
propriedade DataRowView.RowVersion obtém a versão atual da descrição
de um DataRow. Os valores possíveis são:
|
Você pode usar a coluna timestamp (rowversion) de uma linha para determinar se qualquer valor na linha foi alterado desde a última vez que ela foi lida.
Você pode obter o valor do timestamp atual para um banco de dados usando @@DBTS.
TimeStamp é um tipo de dado que
expõe de forma automática valores únicos e binários em um banco de dados. O
timestamp é gerado como um mecanismo de controle de versão de uma linha. O
tamanho armazenado é de 8 bytes. Cada banco de dados possui um contador que é incrementado para cada operação de inclusão ou alteração que é realizada na tabela que contém a coluna timestamp. @@DBTS - retorna o valor do tipo de dado timestamp atual para o banco de dados atual. |
Você pode simplificar a cláusula WHERE de sua consulta de atualização dando suporte a colunas timestamp. A coluna timestamp do SQL Server não contém a informação de data e hora , ela contém a data binária que é única em um banco de dados, Incluindo o valor da coluna rowversion em sua consulta SELECT você terá condições de tratar a concorrência de atualização de forma correta.
Você pode definir a coluna rowversion em sua tabela e a qualquer momento que o conteúdo da linha for alterado, o SQL Server irá modificar o valor da coluna rowversion para aquela linha. Você pode incluir uma coluna rowversion na tabela do seu bando de dados e alterar as consultas de atualização e inclusão de forma a sensibilizar a coluna rowversion.
Vejamos um exemplo:
Eu vou usar o Management Studio 2005 para executar os comandos SQL e criar as tabelas e as consultas no banco de dados Teste.
Nota: Veja como usar o Management Studio no artigo: .NET 2005 - Usando o SQL Server Management Studio
1- Criando a tabela Produto com a columa do tipo rowversion
Abra o Management Studio e selecionando o banco de dados Teste digite e execute a consulta SQL abaixo:
Após a execução veja que a tabela Produto foi criada com a coluna chamada RowVrsn do tipo rowversion - timestamp e not null.
2- Criando a stored procedure de inclusão dp_Product_ins
Este procedimento armazenado inclui valores na tabela Product.
3- Criando a stored procedure de atualização db_Product_upd
Este procedimento armazenado atualiza a tabela Product e usa a rowversion.
Nota: Têm dúvidas sobre Stored Procedures ? Veja o artigo : VB.NET - Brincando com Stored Procedures no SQL Server
Após criar os procedimentos armazenados com suporte a rowversion você pode usar uma combinação de chave primária e coluna rowversion na cláusula WHERE de suas consultas de atualização e de inclusão para ter certeza de que as alterações feitas por outro usuário não serão sobrepostas.
Você pode examinar o valor de retorno de uma instrução ExecuteNonQuery para verificar se a linha foi atualizada e informar a ocorrência ao usuário. Veja um exemplo de como criar este código abaixo:
Public RowVersion(8)
As Byte Public Sub seuMetodo() ..... Dim cn As SqlConnection = New SqlConnection(ConfigurationSettings.AppSettings("ConnectionString")) Dim cmd As SqlCommand = New SqlCommand("usp_SuaStoredProcedure", cn) cmd.CommandType = CommandType.StoredProcedure cmd.Parameters.Add("@Codigo", Me.Codigo) cmd.Parameters.Add("RETURN_VALUE", SqlDbType.Int).Direction = ParameterDirection.ReturnValue With cmd.Parameters.Add("@RowVersion", SqlDbType.Timestamp) .Value = Me.RowVersion .Direction = ParameterDirection.InputOutput End With Try cn.Open() cmd.ExecuteNonQuery() If CInt(cmd.Parameters("RETURN_VALUE").Value) = -1 Then ' a linha foi modificada por outro usuário então lança uma exceção.! Throw New ModificadaPorOutroUsuarioException End If Me.RowVersion = CType(cmd.Parameters("@RowVersion").Value, Byte()) Catch ex As SqlException Throw New Exception("erro no método.", ex) Finally cn.Close() End Try End Sub |
Obs: No exemplo acima estou usando um tratamento de exceção personalizado. Você deve então criar uma classe chamada ModificadaPorOutroUsuarioException com o seguinte código:
Public Class
ModificadaPorOutroUsuarioException Inherits System.ApplicationException Public Sub New() MyBase.New("A linha já foi modificada por outro usuário") End Sub Public Sub New(ByVal InnerException As Exception) MyBase.New("A linha já foi modificada por outro usuário!",InnerException) End Sub End Class |
Creio que com estas dicas você têm todas as condições de planejar o tratamento da concorrência em suas aplicações usando uma técnica apurada e segura.
Eu sei é apenas VB .NET mas eu gosto...
José Carlos Macoratti