23 de diciembre de 2017

Buffering de Datos y Multiusuarios (Parte II)

Autor: Doug Hennig
Traducido por: Germán Giraldo G.


Manejo de Errores

Continuando con el ejemplo de “Bill y Mary”, el código ejecutado cuando Bill hace clic en el botón “Guardar” usa la función tableupdate() para tratar de escribir el buffer al registro. Recuerde que Mary editó el registro y guardó sus cambios mientras Bill estaba editando el mismo registro. Cuando Bill hace clic en “Guardar”, tableupdate() devolverá .F., significando que el no escribió el buffer. Por qué? VFP no escribirá el buffer al registro bajo las siguientes condiciones:

  1. Otro usuario haya cambiado y guardado el registro mientras este usuario lo estaba editando (como sucedió en el ejemplo). VFP automáticamente compara oldval() y curval() para cada campo. Si detecta alguna diferencia, nosotros tenemos un conflicto..
  2. El usuario ha entrado un duplicado del valor una clave primaria o candidata.
  3. Se violó una regla de un campo o tabla, o un campo que no soporta valores null es null.
  4. Falló un desencadenante (trigger).
  5. Otro usuario ha bloqueado el registro. Esto puede minimizarse evitando el bloqueo manual de registros con rlock() y usando el mismo mecanismo de bloqueo para buffer, para una tabla, en todos los formularios y programas que la acceden.
  6. Otro usuario cambió el estado borrado del registro.
Usted debe decidir qué hacer cuando tableupdate() falla. También, si su aplicación le permite al usuario hacer clic en botones “Siguiente” o “Anterior” mientras está editando un registro y aquellas funciones no usan tableupdate(), usted debe manejar el error que ocurrirá cuando se intenta el guardado automático. En ambos casos, el lugar apropiado para manejar esto en un una rutina que atrape los errores.

El manejo de errores ha sido mejorado en VFP. La vieja forma para atrapar un error (la cual usted aún puede usar en VFP) es usar el comando on error para especificar un procedimiento que se ejecute cuando ocurre un error. Esta rutina de error podría típicamente podría mirar en error() y message() para determinar qué sucedió, y tomar la acción apropiada. VFP ahora provee un mecanismo automático de manejo de errores: el método Error. Si existe un método Error para un objeto o formulario, él se ejecutará automáticamente cuando ocurre un error sin tener que atraparlo manualmente. aerror() es una nueva función que ayuda a entender que está mal. Usted le pasa un nombre de matriz y el crea o actualiza la matriz con los siguientes elementos:
Elemento Tipo Descripción
1 Numeric El número de error (la mismo que error()).
2 Character El mensaje de error (lo mismo que message()).
3 Character El parámetro con error (por ejemplo, un nombre de campo) si el error tiene uno (lo mismo que sys(2018)) o .NULL. si ninguno.
4 Numeric or Character El área de trabajo en la cual ocurrió el error si apropiado, de lo contrario .NULL.
5 Numeric or Character El desencadenante que falló (1 para insert, 2 para update, o 3 para delete) si falla un desencadenante (error 1539), o .NULL. si no.
6 Numeric or Character .NULL. (usado para errores OLE y ODBC).
7 Numeric .NULL. (usado para errores OLE).
Por ejemplo, aerror(laERROR) creará o actualizará una matriz llamada laERROR. Aquí están los errores comunes que pueden ocurrir cuando VFP intenta escribir el buffer a la tabla:
Error # Mensaje de Error Comentario
109 Registro está siendo usado por otro
1539 Falló Desencadenante (Trigger) Verifique elemento 5 para determinar cuál trigger falló
1581 Campo no acepta valores nulos Verifique elemento 3 para determinar cuál campo está involucrado.
1582 Violada regla de validación de Campo Verifique elemento 3 para determinar cuál campo está involucrado.
1583 Violada regla de validación de Registro
1585 Registro fue modificado por otro
1884 Violada unicidad de índice. Verifique elemento 3 para determinar cuál campo está involucrado.
El manejo de estos errores es directo: informe al usuario el problema y envíelo al modo editar para corregir el problema o cancelar. Para el error #1585 (registro fue modificado por otro), hay varias formas en que puede manejar el error:
  1. Usted le informa a alguien que otro ha modificado el registro y entonces cancela su edición usando tablerevert(). Yo sospecho que la mayoría de usuarios no estarían felices con esta estrategia <g>.
  2. Puede forzar la actualización del registro usando tableupdate(.F., .T.). Esto causa que se sobre-escriban los cambios de otros usuarios con las modificaciones del usuario actual. Este usuario podría estar feliz, pero los otros usuarios probablemente no lo estarían.
  3. Puede mostrar en una copia del mismo formulario los cambios que el otro usuario ha hecho al registro (fácil de hacer con la habilidad de VFP para crear múltiples instancias del mismo formulario). El usuario puede decidir entonces si los cambios del otro usuario deberían mantenerse o no, y usted puede usar tableupdate(.F., .T.) para forzar la actualización o tablerevert() para cancel.
  4. Un esquema mas inteligente involucra determinar si nosotros tenemos un conflicto “real” o no. Por “real” yo quiero decir: ambos usuarios cambian el mismo campo o no. Si los campos que ellos actualizan son diferentes, podríamos informar a VFP que sólo actualice el campo que este usuario ha cambiado, manteniendo intactos los cambios del otro usuario. Un ejemplo podría se un sistema de proceso de órdenes. Un usuario puede haber editado la descripción de un producto mientras otro usuario ha entrado una orden para el producto, por esta razón disminuye la cantidad en existencia. Estos cambios no son mutuamente excluyentes —si hacemos la actualización de nuestra tabla menos gruesa (esto es, no actualizamos un registro completo al mismo tiempo, sólo los campos que modificados), podemos satisfacer a ambos usuarios.
Aquí está cómo trabaja la lógica:
  1. Hallar un campo donde oldval() es diferente que curval(), esto significa que el campo fue editado por otro usuario. Si el valor buffered de los campos es el mismo de oldval(), este usuario no puede cambiar el campo, así podemos evitar sobre-escribir su nuevo valor poniendo el valor buffered como curval().

  2. Hallar un campo donde el valor buffered sea diferente a oldval(). Este es un campo que el usuario ha modificado Si oldval() es igual a curval(), el otro usuario no ha cambiado este campo, así podemos sobre-escribirlo con seguridad.

  3. Si hallamos un campo donde el valor buffered es diferente del valor oldval() pero es el mismo de curval(), ambos usuarios hicieron el mismo cambio. Esto puede verse improbable, un ejemplo puede ser cuando alguien envía un aviso de cambio de domicilio a una compañía y de algún modo dos usuarios deciden poner al día el registro al mismo tiempo. Debido a que los cambios son idénticos, nosotros podríamos estar habilitados a sobre-escribir el campo. Sin embargo, en el caso de una cantidad que está siendo actualizada por la misma cantidad (por ejemplo, dos órdenes por la misma cantidad fueron entradas al mismo tiempo), usted no desearía sobre-escribir el campo, y debería considerar esto como un conflicto “real”.
  4. Si tenemos un caso donde el valor buffered de un campo es diferente de ambos oldval() y curval(), además oldval() y curval() no son los mismos (iguales), ambos usuarios han cambiado el mismo campo pero con diferentes valores. En este caso, tenemos un conflicto “real”. Usted tiene que decidir cómo manejar el conflicto.
  5. En el caso de una cantidad de inventario existente o cuenta de balance, una posibilidad es aplicar al valor buffered el mismo cambio que el otro usuario ha hecho. Por ejemplo, si oldval() es 10 y curval() es 20, el otro usuario ha incrementado la cantidad por 10. Si el valor buffered es 5, este usuario está disminuyendo la cantidad en 5. El nuevo valor buffered sin embargo debería ser value + curval() - oldval(), o 15.
  6. En el caso de campos Fecha, las reglas de negocios y el sentido común podrían ayudar. Por ejemplo, en un programa de citas de pacientes con un campo conteniendo la fecha de la próxima visita de un paciente, la primera de las dos fechas en conflicto es probablemente la correcta para usar, a menos que sea anterior a la fecha actual, en cuyo caso la fecha posterior será la correcta.
  7. Otros tipos de campos, específicamente campos Character y Memo, a menudo no pueden resolverse sin preguntar al usuario que decida si sobre-escribe los cambios del otro usuario o abandona los suyos. Permitiéndole al usuario ver los cambios del otro usuario (como se mencionó antes) puede ayudarlo a tomar la decisión.
Aquí tiene algún código que resuelve este tipo de conflictos (este código asume que nosotros hemos determinado que el problema es un error #1585, el registro ha sido modificado por otro usuario):

* Verifica cada campo para ver cuál tiene un conflicto.
llConflict = .F.
for lnI = 1 to fcount()
   lcField      = field(lnI)
   llOtherUser  = oldval(lcField)   <> curval(lcField)
   llThisUser   = evaluate(lcField) <> oldval(lcField)
   llSameChange = evaluate(lcField) == curval(lcField)
   do case
     * Otro usuario ha editado este campo pero este usuario,
     * no ha grabado los nuevos valores.
     case llOtherUser and not llThisUser
        replace (lcField) with curval(lcField)
     * Otro usuario no ha editado este campo, o ambos han hecho
     * el mismo cambio, así no necesitamos hacer algo.
     case not llOtherUser or llSameChange
         * Uh-oh, ambos usuarios han cambiado este campo, pero a
         * diferentes valores.
     otherwise
         llConflict = .T.
     endcase
next lnI
* Si tenemos un conflicto, manejarlo.
if llConflict
  lnChoice = messagebox('Otro usuario también cambió este ' + ;
             'registro. Desea sobre-escribir sus cambios (Si), ' + ;
             'no sobre-escribir pero ver los cambios (No), o cancelar ' + ;
             'sus cambios (Cancelar)?', 3 + 16, ;
             'Problema Guardando Registro!')
   do case
   * Sobre-escribir los cambios.
      case lnChoice = 6
         = tableupdate(.F., .T.)
      * Ver los cambios: trayendo otra instancia del formulario.
      case lnChoice = 7
         do form MYFORM name oName
      * Cancelar los cambios.
      otherwise
         = tablerevert()
      endcase
    * Sin conflictos, entonces forzar la actualización.
else
    = tableupdate(.F., .T.)
endif llConflict

No hay comentarios. :

Publicar un comentario

Los comentarios son moderados, por lo que pueden demorar varias horas para su publicación.