18 de diciembre de 2017

Buffering de Datos y Multiusuarios (Parte I)

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

Generalidades

En FoxPro 2.x, los desarrolladores editaban los registros usando scatter memvar, editando las variables de memoria, y gather memvar. El propósito de de esta edición indirecta de campos era proteger el registro haciendo buffering. Con Visual FoxPro, el buffering de datos está incluido, así los campos pueden editarse directamente. Esta sesión discutirá cómo trabaja el buffering de datos y explora estrategias para seleccionar cuál mecanismo de buffering usar y cómo manejar conflictos multiusuario.


Introducción

Si usted ha pasado algún tiempo trabajando con VFP, una de las cosas que probablemente ha aprendido es que aunque usted puede continuar haciendo las cosas en la “vieja” forma si usted lo desea, VFP le da una forma mejor para realizar la misma tarea. Cómo editar los registros en un formulario es un perfecto ejemplo de esto.

Aquí está la “vieja” forma que yo usaba para escribir código para editar un registro en una pantalla de entrada de datos:

  1. La pantalla tiene objetos get para variables de memoria con el mismo nombre de los campos de la tabla (por ejemplo, M.CUST_ID y M.NAME).
  2. Cuando el usuario posiciona la tabla en un registro particular (por ejemplo, usando el botón “Siguiente”), usar scatter memvar para transferir el registro a las variables de memoria y show gets para refrescar los valores en la pantalla. El usuario no puede editar las variables (ellas están deshabilitadas o tienen una cláusula when que se evalúa a F.) a causa de que el usuarios actualmente está en el modo “ver”.
  3. Cuando el usuario elige el botón “Editar”, trata de bloquear el registro; muestra un mensaje apropiado si no podemos. Verificar el valor de cada campo contra su variable de memoria —si ellos no coinciden, otro usuario podría haberlo editado y guardado el registro desde que nosotros lo mostramos por primer vez. En ese cado, muestra un mensaje apropiado y usa scatter memvar y show gets de nuevo para que el usuario vea el contenido actual del registro.
  4. Si los campos y las variables de memoria coinciden, se habilitan los objetos get o hace que su cláusula when se evalúe a .T. para que el usuario pueda editar las variables.
  5. Cuando el usuario elige el botón “Guardar”, se hace alguna validación para asegurar que todo se entró de acuerdo a sus reglas, entonces usar gather memvar para actualizar el registro desde las variables de memoria y desbloquea el registro. Deshabilitar los objetos get o hacer que su cláusula when se evalúe a .F. para que el usuario esté de nuevo en el modo “ver”.
Note que en este esquema nosotros no hacemos un read directo contra el registro. En su lugar, nosotros le permitimos al usuario editar variables de memoria y sólo copia aquellas variables de memoria al registro si todo está bien. La razón para usar este método es proteger la tabla; nosotros no permitimos que cualquier datos se almacene a menos que pase todas la reglas. También note que el registro está bloqueado mientras el usuario está editando las variables de memoria. Esto evita que otros usuarios editen el mismo registro al mismo tiempo. Hacerlo, sin embargo, adolece del síndrome de “salí a comer” —si el usuario empieza a editar, entonces se va a comer, el registro permanece bloqueado, no disponible para que otros usuarios lo editen. Desde luego, esta no es la única forma de editar registros. Usted podría hacer el bloqueo justo antes de guardar el registro en lugar de cuando inicia el modo editar. Esto minimiza el tiempo que el registro permanece bloqueado, permitiéndole a otros usuarios accederlo. Esto tiene sus propios inconvenientes, aunque: si el usuario edita las variables y hace clic en “Guardar”, qué sucede si mientras tanto algún otro usuario ha editado el registro? Usted sobre-escribe los cambios? Usted evita que el registro se guarde? Esto es un asunto de diseño que usted debe manejar caso-por-caso. El único propósito de todo este esfuerzo es proteger los datos. Si usted estuviera escribiendo una aplicación que sólo usted usará siempre, usted probablemente lo haría mucho mas simple —sólo un read contra el campo directamente en el registro. Esto hace que la pantalla actúe en forma equivalente a un browse, debido a que todo lo que usted digita se hace directamente en el registro. Sin embargo, debido a que nosotros no podemos confiar en esos usuarios molestos para saber lo que ellos pueden y no pueden entrar, nosotros tenemos que proteger los datos construyendo un "cortafuego" entre el usuario y la tabla. Crear este "cortafuego" en FoxPro 2.x tomaba una cantidad considerable de código. VFP provee un mecanismo “cortafuegos” que nos da lo mejor de ambos mundos: read directo contra un registro mientras que sólo permite que los datos se escriban después de pasar todas las pruebas. Este mecanismo es buffering.

Buffering

Usar variables de memoria para mantener el contenido de un registro puede considerarse como crear un buffer de datos. Los datos se transfieren desde el registro al “buffer” usando scatter memvar y desde el “buffer” al registro con gather memvar. VFP no sólo puede hacer este tipo de buffering de un único registro (llamado buffering de fila o de registro) automáticamente, él también soporta otro tipo de buffering (llamado buffering de tabla) en el cual múltiples registros se accedan a través de un buffer. El buffering de registro se usa normalmente cuando usted desea acceder o actualizar un único registro a la vez. Esto es común en mecanismos de entrada de datos como los descritos antes: el usuario puede mostrar o editar un único registro en el formulario. El buffering de tabla debe elegirse para actualizar varios registros a la vez. Un ejemplo común es un formulario con cabecera-detalle de factura. Usando buffering de tabla para la tabla detalle de factura, usted puede permitirle al usuario editar tantas líneas de detalle como él desee, y entonces guardar o cancelar todos los registros de detalle a la vez. Además de los dos mecanismos de buffering, hay dos mecanismos de bloqueo. La “vieja” forma que describí antes puede considerarse un esquema de bloqueo pesimista —el registro se bloquea tan pronto como el usuario elige “Editar”, y permanece bloqueado hasta que él elige “Guardar”. Esto asegura que nadie mas puede hacer cambios al registro mientras el usuario lo esté haciendo, esta forma puede o no ser una buena cosa, dependiendo de su aplicación. El otro método que yo describí antes es un mecanismo de bloqueo optimista —el registro sólo se bloquea por el breve tiempo que toma escribir el registro, y se desbloquea inmediatamente. Esto maximiza la disponibilidad del registro (esto también se conoce como maximizar la simultaneidad) pero significa que tenemos que manejar conflictos que ocurren si dos usuarios editan el registro al mismo tiempo. Como veremos en un momento, actualmente esto es fácil de hacer en VFP, así el buffering optimista probablemente será el mecanismo elegido para la mayoría de las aplicaciones. Debido a que los registros pueden ser buffered automáticamente, ya no es necesario usar el mecanismo de “buffer manual”. En otras palabras, ahora nosotros podemos hacer read directamente contra los campos en el registro sin preocuparnos por el mantenimiento de variables de memoria para cada uno. Para guardar los cambios, simplemente le informamos a VFP que escriba el buffer en la tabla, y para cancelar los cambios, le informamos que no lo haga. En un momento veremos cómo hacerlo. VFP implementa el buffering creando un “cursor” cuando se abre una tabla. El cursor se usa para definir propiedades para la tabla. En el caso de tablas locales, la única propiedad para el cursor es cómo se realiza el buffering; para vistas y tablas remotas hay propiedades adicionales que van mas allá del alcance de esta sesión. Estas propiedades se establecen usando la función cursorsetprop() y se examinan con cursorgetprop(). Veremos resumidamente el uso de estas funciones. El buffering de tabla tiene una implementación interesante respecto a agregar registros: cómo se agregan los registros al buffer, se les asigna un número de registro negativo. recno() devuelve -1 para el primer registro agregado, -2 para el segundo, y así sucesivamente. Puede usar go con un número negativo para posicionar el buffer en el registro agregado apropiado. Esto tiene una implicación en las rutinas que manejan números de registro —en lugar de probar para between(lnRecno, 1, reccount()) para asegurar que lnRecno es un número de registro válido, usted ahora probará para between(lnRecno, 1, reccount()) or lnRecno < 0.
Usar Buffering
Buffering está desactivado por definición, así VFP actúa como FoxPro 2.x en términos de cómo se escriben las actualizaciones en una tabla. Para usar buffering, usted debe activarlo específicamente. Buffering está disponible para ambos: tablas libres y aquellas unidas a una base de datos. Buffering requiere que usted establezca set multilocks on debido a que por definición también está desactivado; usted obtendrá un mensaje de error si olvida hacer esto. Puede poner multilocks = on en su CONFIG.FPW o usar la función Options en el menú Herramientas para guardar esta configuración por definición. Buffering se controla usando cursorsetprop('Buffering', <n>, <Alias>). No tiene que especificar <Alias> si está configurando buffering para la tabla actual, <n> es uno de los siguiente valores dependiendo del método de buffering y bloqueo que desee usar:


Buffering/Método de Bloqueo <n>
sin buffering 1
registro, pesimista 2
registro, optimista 3
tabla, pesimista 4
tabla, optimista 5
Por ejemplo, para habilitar buffering de registro optimista, use cursorsetprop('Buffering', 3). Para determinar qué buffering está en uso en la tabla actual, use cursorgetprop('Buffering'). Para habilitar buffering en un formulario, podría especificar cursorsetprop() para cada tabla en el método Load del formulario, pero la estrategia preferida es establecer la propiedad BufferMode del formulario como optimista o pesimista (el predefinido es “ninguno”). El formulario entonces automáticamente usa buffering de tabla para todas las tablas ligadas a grids y buffering de registro para todas las otras tablas. Si usa un DataEnvironment para el formulario, puede sobre-escribir el BufferMode del formulario para una tabla particular estableciendo su BufferModeOverride como desee. Mientras un usuario esté cambiando los datos de un registro buffered (esté en medio de la edición de un registro), usted tiene acceso no sólo al valor que él ha entrado en cada campo, si no también al valor anterior de cada campo y su valor actual (el valor actualmente en disco). Dos nuevas funciones, oldval() y curval(), fueron agregadas para este propósito. Aquí está cómo se obtienen los valores apropiados:
Para obtener: Use:
el valor que el usuario ha entrado (el valor en el buffer) <fieldname> or <alias.fieldname>
el valor antes que el usuario cambiara algo oldval('<fieldname>')
el valor actual en el registro curval('<fieldname>')
curval() y oldval() sólo pueden usarse con buffering optimista. Usted puede extrañarse que el valor devuelto por curval() podría diferir del devuelto por oldval(). Esto obviamente no ocurriría si sólo un usuario está ejecutando la aplicación. Sin embargo, en una red y con bloqueo optimista, es posible que después de que el usuario ha iniciado la edición de un registro, otro usuario ha editado el mismo registro y ha guardado los cambios. Aquí hay un ejemplo: Bob trae el registro #2 de CONTACTS.DBF y hace clic en el botón “Editar”:
Campo Valor Oldval() curval()
LAST_NAME Jones Jones

Jones

FIRST_NAME Bill Bill Bill


Bob cambia el primer nombre a Sam pero todavía no ha guardado el registro:

Campo Valor Oldval() curval()

LAST_NAME

Jones Jones Jones
FIRST_NAME Sam Bill Bill<

Mary trae el registro #2 de CONTACTS.DBF, hace clic en el botón “Editar”, cambia el primer nombre a Eric, y guarda. En la máquina de Bill:

Field

Value

Oldval()

curval()

LAST_NAME

Jones

Jones

Jones

FIRST_NAME

Sam

Bill

Eric

Observe que FIRST_NAME, oldval('FIRST_NAME'), y curval('FIRST_NAME') devuelven valores diferentes. Teniendo acceso al valor original, el valor buffered, y el valor actual para cada campo en el registro, usted puede:
  1. determinar cuáles campos cambió el usuario comparando el valor buffered con el valor original; y
  2. detectar si otros usuarios en la red han hecho cambios al mismo registro después de iniciar la edición, comparando el valor original con el valor actual.
Si no le interesan los valores anterior y actual y sólo desea detectar si un campo fue editado por el usuario, use getfldstate(). Esta nueva función devuelve un valor numérico indicando si algo del registro actual ha cambiado. getfldstate() se llama en la siguiente forma: getfldstate(<FieldName> | <FieldNumber> [, <Alias> | <WorkArea>]) y devuelve uno de los siguientes valores:
Valor

Descripción

1 Sin cambios
2 El campo fue editado el estado de borrado del registro ha cambiado
3 Se agregó un registro pero el campo no fue editado y el estado de borrado no ha cambiado.
4 Se agregó un registro y el campo fue editado o el estado de borrado del registro ha cambiado.
Cambiar el estado de borrado significa ambos: borrar o recuperar (delete/recall) el registro. Note que borrar y recuperar inmediatamente el registro resultará en un valor de 2 o 4 aunque no haya un efecto de red para el registro. Si no especifica un alias o área de trabajo, getfldstate() opera sobre la tabla actual. Especifique 0 para <FieldNumber> para devolver el estado de agregado o borrado del registro actual. Si especifica -1 para <FieldNumber>, la función devolverá una cadena con el primer dígito representando el estado de la tabla y un dígito para el estado de cada campo. En el ejemplo mencionado antes, donde Bill edita el segundo campo, getfldstate(-1) podría devolver “112”. El primer dígito indica que el registro no fue agregado o borrado, el segundo que el primer campo no ha cambiado, y el tercero que el segundo campo ha cambiado.

Escribir un Registro Buffered

Continuando con el ejemplo anterior, ahora Bill hace clic en el botón “Guardar”. Cómo informamos a VFP que escriba el buffer al registro? Con buffering de registro, la tabla se actualiza cuando usted mueve el puntero del registro o usa la nueva función tableupdate(). Con buffering de tabla, moviendo el puntero del registro no actualiza la tabla (debido a que todo el punto del buffering de tabla es que varios registros son buffered al mismo tiempo), así la forma usual es llamar la función tableupdate(). Es mejor usar tableupdate() aún para buffering de registro debido a que usted tiene mas control sobre lo que sucede. tableupdate() devuelve .T. si el buffer se escribió con éxito al registro. Si el buffer de registro no ha cambiado (el usuario no ha editado algún campo, agregado un registro, o cambiado el estado de borrado para el registro), tableupdate() devuelve .T. pero no hace nada. tableupdate() puede recibir unos pocos parámetros opcionales: tableupdate(<AllRows>, <Forced>, <Alias> | <Workarea>) El primer parámetro indica qué registros actualizar: .F. informa que sólo se actualice el registro actual, mientras que .T. significa actualizar todos los registros (sólo tiene efecto si se usa buffering de tabla). Si el segundo parámetro es .T., cualquier cambio de otro usuario se sobre-escribirá por los cambios del usuario actual. A menos que se especifique el tercer parámetro, tableupdate() actualizará la tabla actual. Cómo cancelar los cambios que ha hecho el usuario? Con la estrategia de variables de memoria, usted sólo usa scatter memvar de nuevo para restaurar las variables de memoria a los valores almacenados en disco. Con buffering, use la función tablerevert() para realizar lo mismo para el buffer.

2 comentarios :

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