Dica de Design: criando propriedades com UnderlyingValues e OriginalValues

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

Dica de Design: criando propriedades com UnderlyingValues e OriginalValues

Você deve ter visto que os objetos Field de ADO possuem as propriedades UnderlyingValue o OriginalValue. Estas propriedades são úteis de diferentes formas. Com elas é possível saber qual o valor anterior de um campo em meio a uma operação de edição, qual o valor originalmente trazido do banco de dados para o campo e mesmo armazenar informações para tratar colisão de dados que ocorram no momento da atualização da base de dados. Ter estes valores nas propriedades de um objeto de acesso a dados pode enriquecer suas possibilidades de manipular o objeto e não custa muito em termos de código, porque a maior parte do código é repetitiva e basta copiar e colar.

Para implementar estas propriedades de propriedades, como podemos chamá-las, você pode criar uma variável do tipo matriz de variantes no módulo da classe. Esta matriz terá tantas linhas quantas sejam as propriedades para as quais você quer implementar estes recursos. As colunas serão em número de três colunas. Na primeira coluna estarão os elementos que conterão os valores atuais das propriedades, na segunda coluna ficarão os valores correspondentes aos UnderlyingValues e na terceira coluna os valores correspondentes aos OriginalValues. Chamemos a esta matriz de Properties. Supondo que tenhamos um objeto Cliente com cinco propriedades. A matriz Properties seria definida assim:

Private Properties( 1 To 5, 1 To 3) As Variant

Para facilitar o acesso aos elementos da matriz Properties criamos duas enumerações públicas: uma que usaremos para referenciar as colunas e outra para referenciar as linhas da matriz Properties. A enumeração para referenciar as colunas pode ser uma só para todos os objetos em que usarmos este esquema. Veja abaixo:

Public Enum PROP_VALUES

         CURRENT_VALUE = 1 ' valor atual da propriedade

        UNDERLYING_VALUE = 2  ' valor anterior à edição que está em curso

        ORIGINAL_VALUE = 3  'valor inicialmente trazido do banco de dados

End Enum

Para referenciar as linhas da matriz teremos que ter em cada objeto uma enumeração própria. No caso do objeto Cliente suposto acima, poderíamos ter uma enumeração em que cada valor corresponde a uma propriedade e à linha de Properties em que serão armazenados os valores da propriedade. Veja como seria:

Public Enum PROP_CLIENTE

       CLI_ID = 1

        CLI_NOME = 2

       CLI_CPF = 3

       CLI_ENDERECO = 4

       CLI_FONE = 5

 End Enum

Para exemplificar o uso disto, ao definirmos os procedimentos da propriedade "Nome" faríamos como abaixo:

Public Property Get Nome () As String

    Nome = Cstr(Properties(CLI_NOME, CURRENT_VALUE))

End Property

Public Property Let Nome ( NewValue As String )

    Properties(CLI_NOME, CURRENT_VALUE) = NewValue

End Property

Para acessarmos os valores de UnderlyingValue e OriginalValue teríamos rotinas genéricas válidas para todas as propriedades e que são sempre as mesmas em qualquer objeto, mas mudando apenas o tipo de enumeração usada no argumento. Veja abaixo.

Public Property Get OriginalValue(prop As PROP_CLIENTE) As Variant

    OriginalValue = Properties(prop, ORIGINAL_VALUE)

End Property


Public Property Get UnderlyingValue(prop As PROP_CLIENTE) As Variant

    UnderlyingValue = Properties(prop, UNDERLYING_VALUE)

End Property

Vejamos então como tirar proveito destas informações adicionais que você pode agora armazenar sobre suas propriedades. Supondo que você esteja expondo um objeto Cliente em uma janela de edição. O usuário vai alterando os valores dos campos de dados e você vai transferindo estes valores para as propriedades do objeto Cliente subjacente. Ao clicar em OK,  o usuário está sinalizando que quer tornar suas modificações definitivas. Mas se der um clique em Cancelar ou fechar a janela sem concluir a operação de edição, você pode restaurar as propriedades do objeto ao que eram antes de se iniciar a edição. Você faz isto buscando os valores das propriedades nos seus  UnderlyingValues. No caso de o usuário confirmar as alterações, você pode salvar os valores atuais também nos UnderlyingValues de cada propriedade e comandar ao Objeto que se grave no banco de dados.  Veja abaixo uma rotina que atualizaria os UnderlyingValues com os valores atuais de cada propriedade:

Private Sub UpdateUnderlyingValues()

    Dim i As Long

    For i = 1 To UBound(Properties, 1)
        Properties(i, UNDERLYING_VALUE) = Properties(i, CURRENT_VALUE)
    Next i

End Sub

No caso do usuário cancelar a edição clicando em Cancelar, a rotina abaixo poderia ser usada para restaurar os valores das propriedades:

Public Sub CancelEditing()     

      Dim i As Long

     For i = 1 To UBound(Properties, 1)

         Properties(i, CURRENT_VALUE) = Properties(i, UNDERLYING_VALUE)

     Next i 

End Sub

Se objeto encontrar um conflito de dados durante a atualização da base de dados ( veja como tratar isto no artigo Viva sem bloquear registros), você pode pesquisar as propriedades atuais do objeto na base de dados e armazenar estes valores nos UnderlyingValues de cada propriedade. Ao retornar um erro de conflito ou colisão de dados, uma rotina de tratamento de erros dentro do objeto poderá usar os UnderlyingValues e OriginalValues para tratar o erro. Ao comparar cada valor de UnderlyingValue com o seu correspondente OriginalValue, será possível descobrir quais valores foram alterados por outro usuário desde que o registro foi inicialmente lido. Com estas informações é possível informar o usuário atual do que ocorreu e pedir-lhe uma tomada de decisão sobre o que fazer. Caso o usuário queira sobre-gravar as informações atuais com as suas, mesmo sendo as mais atigas, você pode igualar as propriedades OriginalValues às retornadas do banco de dados em UnderlyingValues para evitar novos conflitos e atualizar mesmo assim. Se quiser preservar as atualizações do outro usuário, iguale os valores das propriedades onde ocorreu conflito com os que estão em UnderlyingValues (valores atuais no banco de dados). Feito isto, comande novamente a gravação do objeto. Veja abaixo uma rotina que pode ser usada para sincronizar o estado atual do objeto com as informações colhidas da base de dados após uma constatação de conflito. O argumento Keep é usado para sinalizar se o que se quer é a sobre-gravação total dos dados no BD ou se as alterações feitas por outros devem ser preservadas.

Private Sub AdjustToResyncData(ByVal keep As Boolean)

    Dim i As Long

    For i = 1 To UBound(Properties)
         If Properties(i, CURRENT_VALUE) <> _
              Properties(i, UNDERLYING_VALUE) Then ' houve conflito de dados
              If keep Then 'mantém alterações de outros usuários
                     Properties(i, ORIGINAL_VALUE) = Properties(i, UNDERLYING_VALUE)
                     Properties(i, CURRENT_VALUE) = Properties(i, UNDERLYING_VALUE)
              Else ' sobregrava
                     Properties(i, ORIGINAL_VALUE) = Properties(i, UNDERLYING_VALUE)
                     Properties(i, UNDERLYING_VALUE) = Properties(i, CURRENT_VALUE)
              End If
         End If
    Next i

End Sub

Acrescente-se ao que foi visto acima que você pode também criar uma propriedade DataChanged para sinalizar quando o objeto foi modificado em relação ao seu estado original. Veja abaixo como:

Public Property Get DataChanged() As Boolean
    Dim i As Long

    DataChanged = False
    For i = 1 To UBound(Properties)
        If Properties(i, CURRENT_VALUE) <> Properties(i, ORIGINAL_VALUE) Then
            DataChanged = True
            Exit Property
        End If
    Next i

End Property