Buffering, El Vampiro asesino
24/03/2020
Autor: Jim Booth
En FoxPro 2.x hemos gastado un montón de esfuerzos para preservar los valores de los registros de una tabla, así dábamos la oportunidad al usuario de deshacer las modificaciones realizadas. Visual FoxPro nos ofrece un mecanismo para realizar esta tarea mas fácilmente. La herramienta es el bufering de datos. En este artículo y el siguiente examinaremos los matices del bufering de datos y las otros necesidades en Visual FoxPro. El tema de este mes son los principios básicos del bufer de datos.
Usar buffering o nó
La primera pregunta es, ¿debo usar bufer de datos para todo? ó debo seguir con el SCATTER y GATHER para solucionar el problema? Bien, la respuesta no es simple. SCATTER y GATHER requieren que el rango de las variables o matrices sean definido correctamente, y así sean visibles a todos los objetos que necesiten acceso a ellas. Puesto que las variables se definen por defecto como privadas (es decir, son destruidas cuando la rutina que las creo termina), entonces tenemos un problema.
Si en el método de un formulario hago un SCATTER MEMVAR, las variables creadas se van fuera de rango (es decir se destruyen), tan pronto como el método finaliza, a menos que declare las variables como PUBLIC (publicas) antes de hacer el SCATTER. Declarar variables como publicas tiene otra serie de problemas que van más allá de este artículo, así que vamos a ponernos de acuerdo en que no queremos declarar las variables como públicas a menos que no haya absolutamente otra forma para hacerlo.
Con los controles de VisualFoxpro y su capacidad de vincularse a datos, tiene sentido tener los datos disponibles durante la creación del control. La secuencia de creación es tal, que el Entorno de Datos (DE) de un formulario se crea antes de que se creen los controles. Esto permite al Entorno de Datos abrir los cursores de datos, antes de que los controles intenten vincularse con sus controlsources. Si nosotros vinculamos los controles directamente con los campos del cursor todo funcionará bien, sin embargo, si estamos vinculándolos a variables de memoria debemos asegurarnos que dichas variables existen en el momento que los controles son creados.
Esta creación de variables puede ser realizada en el evento Load del formulario (el cual se dispara antes de que los controles sean creados), declarando las variables como públicas y entonces haciendo un scatter . Vaya! aquí esta el tema de las variables publicas (donde está un vampiro asesino cuando necesitas uno).
El bufer de datos nos da lo mejor de ambos mundos, podemos vincularlo a los campos y podemos controlar la actualización o deshacer las modificaciones del registro sobre el disco. Vinculándolo directamente a los campos, todo el problema del rango de las variables desaparece.
¿Hay alguna situación donde puedes no desear el buffering? Si, si una parte de las necesidades es que las modificaciones son grabadas inmediatamente e irrevocablemente. En esta situación no usar el bufer de datos es el camino a seguir. También, si usas una tabla solo para lectura, entonces puedes definir el buffering a ninguno.
¿Que es el bufer (buffering) de datos?
En FoxPro 2.x, cuando definíamos nuestros GETs contra los campos de una tabla, decíamos que hacíamos ediciones directas. Las llamábamos directas porque asumíamos que se editaban directamente los campos de la tabla. No teníamos control sobre el proceso de actualización. De hecho, Foxpro 2.x hacia la edición del registro en un bufer de memoria que podía ser después usado para actualizar la tabla. En Foxpro 2.x no teníamos control sobre el bufer, él podría ser escrito cuando Foxpro se saliera de él y no había ninguna manera de que pudiéramos parar lo que estuviera sucediendo.
En Visual FoxPro tenemos el bufer de datos, que nos da un control sobre este buffer de edición. Nos permite, cuando está activado el buffering, controlar cuando y como se graba el bufer al disco. El bufer de datos es simplemente una tecnología que hace más fácil lo que hacíamos antes.
El bufer de datos de Visual Foxpro añade alguna funcionalidad al proceso. Con el uso de las funciones tales como OldVal() y CurVal() podemos saber que valores tenían inicialmente el bufer y lo que esta grabado en el disco respectivamente. Podremos encontrar mas funciones para mejorar el bufer de datos en VFP tanto como vayamos explorando más.
¿Porque hay 5 ó 6 modos de buffering?
Actualmente hay cinco modos para definir el buffering, pero en la propiedad BufferModeOverride de un cursor del Entorno de Datos podrás ver seis opciones. La opción adicional es "Usar el ajuste del formulario", el cual causa que VFP ponga la propiedad BufferMode como el formulario y se decide en modo tabla o fila en función del tipo de control que se está usando (más información sobre esto más adelante).
Los seis modos de buffering son:
- Ninguno (0)
- Usar configuración del formulario (1)
- Pesimista de Filas (2)
- Optimista de Filas (3)
- Pesimista de Tablas (4)
- Optimista de Tablas (5)
El valor Ninguno (0) desactiva el bufer de datos y provoca que VFP actúe como lo hacía Foxpro 2.x, respecto a la edición de datos. Los otros (del 2 al 5) son Optimista/Pesimista por Fila/Tabla.
Optimista
Con modo de bufer optimista, VFP no bloquea ningún registro cuando empieza la edición. En vez de eso, cuando sucede un intento de actualizar la tabla, VFP comprueba si el registro que se está actualizando es el mismo que el que empezó nuestro bufer. Si son iguales, se realiza la actualización, si no la actualización no es realizada (mas sobre esto mas adelante).
Pesimista
Con modo de bufer pesimista, VFP intenta bloquear un registro cuando se inicia una edición, si se puede bloquear se permite la edición. Si VFP no puede obtener el bloqueo entonces se produce un error ("El registro esta siendo accedido por otro usuario") y se rechaza la edición.
Fila
El bufer de Filas permite ensuciar un solo registro del buffer a un tiempo. Ensuciar, tal y como es usado aquí, significa que el registro ha sido editado en el bufer. Un intento de mover el puntero de registros en un cursor que tiene buffer por filas causara que VFP intente actualizar la tabla.
Tabla
El bufer de Tablas permite que haya múltiples registros sucios en el bufer del cursor. Mover el puntero de registros no tiene ninguna actividad de actualización implicíta.
Combinando Optimista/Pesimista con Fila/Tabla obtienes cuatro modos distintos de buffering a mayores de ninguno.
¿Cual es el modo correcto?
En primer lugar vamos a echar un vistazo en cuanto a Optimista en contra de Pesimista. Pesimista bloquea el registro en el servidor. Pesimista asegura el derecho a grabar antes de que se haya realizado la edición. Esto da lugar a pensar que hay muy buenas razones de peso para preferir el modo de bufer pesimista. Bien, antes de que realicemos conclusiones vamos a ver los inconvenientes del modo de bufer pesimista.
El modo de bufer pesimista bloquea el registro cuando se inicia la edición y mantiene ese bloqueo hasta que sucede la actualización. Esto significa que si María empieza a editar y se marcha a comer, nadie puede trabajar con el registro que María estaba editando. Pesimista utiliza los bloqueos en el servidor de red, lo cual consume recursos del servidor. A menudo el servidor de red tiene un numero limitado de bloqueos simultáneos y si tu numero de usuarios crece, podrás llegar a tener errores de red cuando el modo de bufer pesimista intenta bloquear un registro y no puede.
El modo de bufer optimista no realiza bloqueos seguros hasta que sucede la actualización. Se realiza el bloqueo, se hace la actualización y se libera el bloqueo. Estos bloqueos son mantenidos sencillamente. Pero el modo de bufer optimista puede fallar en la actualización debido a que en ese momento haya un bloqueo pesimista. ¿No es esto una buena razón para preferir pesimista? No, usando el modo de bufer optimista uno puede garantizar su propio bloqueo cuando se inicia la edición y liberar el bloqueo cuando se quiera, así que el modo de bufer optimista puede dar la misma funcionalidad que pesimista. El problema, tal y como yo lo veo, es que el modo de buffer pesimista no me ofrece ningún control sobre que es lo que se debe bloquear y cuando (sucede por arte de magia) mientras que el modo de bufer optimista me da el control. ¡Odio la magia! Así que mi preferencia es optimista para todo. Si necesito garantizar la grabación de información, lo realizaré en mi código.
¿Y acerca de Fila en lugar de Tabla? El mismo razonamiento que utilice para optimista en lugar de pesimista, también puede ser utilizado aquí. Modo de bufer por fila, solo permite ensuciar un registro a un tiempo y realizar una actualización "mágica" cuando se mueve el puntero de registro. El modo de bufer en tablas no realiza ningún tipo de magia, porque permite ensuciar múltiples registros al mismo tiempo, no necesita actualizar cuando el puntero de registros se mueve. Se puede restringir la edición de un solo registro al mismo tiempo, mediante el interface de usuario, no permitiendo moverse a otro registro mientras se está en la edición.
¿Porqué la actualización implicíta es un problema? VFP es orientado a objetos, de tal manera que creamos clases que tienen un comportamiento específico. Tenemos que intentar hacer estas clases genéricas, de modo que puedan ser utilizadas en múltiples lugares y ocasiones. Si creas un formulario, utilizas bufer de filas, y después añades un control basado en una clase que busca algo dentro de un cursor, tu podrás obtener una actualización automática no deseada sobre el bufer. No solo puede suceder, sino que puede ser una pesadilla de ejecutar y depurar. Con bufer en tablas no hay nada de magia y así el problema se convierte en ningún problema.
Así que si leo correctamente lo anterior la conclusión es usar el modo de bufer optimista por tablas en cualquier situación. ¡Vaya! Si esto es lo que hago. Si yo necesito editar solo un registro, lo codificaré. Si necesito garantizar las grabaciones lo codificaré.
¿Donde y como defino el modo del bufer?
Tienes una serie de métodos para definir los modos del bufer. Están en la propiedad BufferMode del formulario, la propiedad BufferModeOverride del cursor, y usando la función CursorSetProp().
Propiedad BufferMode del formulario
La propiedad BufferMode de un formulario tiene tres opciones. Son 0-Ninguno, 1-Pesimista, y 2-Optimista respectivamente. La opción fila o tabla del modo del bufer es manejada por el tipo de control que está vinculádo a la tabla, si se vincula a un grid se utiliza modo de bufering tabla y si se vincula a otro control se utiliza el modo bufer de filas.
Propiedad BufferModeOverride del cursor
La propiedad BufferModeOverride de un cursor permite seis opciones. Son 0-Ninguna, 1-Usar configuración del formulario, 2-Pesimista de Filas, 3-Optimista de Filas, 4-Pesimista de Tablas, 5-Optimista de Tablas. El BufferModeOverride debe ser definido por cada cursor del Entorno de Datos si se quiere uno diferente al 1-Usar configuración del formulario (por defecto).
CursorSetProp()La función CursorSetProp() puede ser utilizada programaticamente para definir el modo del buffer, La función CursorSetProp() tiene la siguiente sintaxis;
CURSORSETPROP(cPropiedad [, eExpresion] [, cAliasTabla | nAreadeTrabajo])Para definir los modos de almacenamiento en bufer, el primer argumento es "BUFFERING", el segundo es el número del modo de bufer como se explico en la sección BufferModeOverride hace un momento, y el último es el nombre del alias del cursor en donde se quiere definir el modo de bufer. Por ejemplo si queremos definir un cursor (con alias Clientes) con modo de almacenamiento optimista de tablas yo haría lo siguiente:
CursorSetProp(“buffering”, 5, ”Customer”)
Para definir el modo de bufer con la función CursorSetProp() debe estar activada previamente la opción SET MULTILOCKSS ON (estará off por defecto a menos que lo hayas cambiado en el cuadro de dialogo Herramientos-Opciones). Definir los modos de almacenamiento en bufer requiere un multilocks explícito, así el formulario hará los bloqueos de múltiples registros por ti..
¿Como se controlan las actualizaciones?
El proceso de actualización está controlado por dos funciones: TableUpdate() y TableRevert(). El primero realiza una actualización del registro y el segundo deshace las modificaciones y devuelve al bufer los datos anteriores en el registro. El mes siguiente investigaremos estas dos funciones en mayor profundidad.
Resumen
Lo podemos hacer por el camino difícil o por el fácil. Utilizar almacenamiento de datos en bufer es el camino mas fácil, una vez que se entienda como funciona. Lleva menos codificación para conseguir los mismos resultados de la tecnología de Foxpro 2.x y permite el aprovechar de las capacidades de vinculación de datos de los controles de VFP. Hay una multitud de opciones referidos al bufer de datos. Un examen cuidadoso de cada una de estas opciones nos harán mas simple la decision.
La historia continua...
En el artículo hemos visto la tecnología de almacenamiento en buferes con Visual FoxPro. Fueron revisados los diversos modos de bufer, y fue presentada una comparación respecto a las ediciones directas de FoxPro 2.x contra el almacenamiento en bufer de VisualFoxPro. En este artículo veremos como se trabaja con las funciones TableUpdate y TableRevert y entenderemos como son usadas con el sistema de almacenamiento en bufer.
Así que... ¿Donde estan los datos realmente?
Con el bufer activado, VisualFoxpro mantiene tres versiones de los datos. La primera son los datos que existen en el archivo del disco, la segunda son los datos que existen en tu bufer de datos, y la última son los datos tal y como estaban cuando empezamos a modificar nuestro bufer. Cualquiera de estas versiones de los datos las tenemos disponibles mediante programación/código. Mas adelante veremos como podemos acceder a ellas.
En el artículo anterior se pudo ver que con el bufer de datos activado, la modificación se realiza en una copia de los datos. La cuestión obvia es ¿Cómo hacer que dicha copia se actualice en la tabla?. Con la función TableUpdate.
Una definición ayudará con la discusión, bufer sucio. Un bufer sucio es un bufer de datos que contiene cambios pendientes que no han sido actualizados en la tabla.
Actualizar o no actualizar
TableUpdate es la función que provoca que los datos de un bufer se escriban sobre una tabla. Esta función tiene (4) argumentos, la sintaxis es,
TABLEUPDATE( [nFilas|lFilas> [, lForzar]] [, cAliasTabla | nÁreaTrabajo] [, cMatrizErrores])Como se puede ver, todos los argumentos de esta función son opcionales. Realizando un TableUpdate sin argumentos actualizará el registro actual de la área de trabajo activa y no avisará de un conflicto de actualización. Vamos a ver los argumentos en detalle.
nFilas | lFilas
En Visual Foxpro 3.0 el primer argumento era lógico y .T. significaba que se debían actualizar todos los registros cambiados, mientras que .F. solo significaba que se actualizará el registro actual. Este argumento lógico todavía funciona en Visual FoxPro 6.0, pero la alternativa numérica nos da un mayor control sobre esto.
El argumento numérico puede ser 0, 1 o 2 y controla como se actualizan los registros. Un argumento de 0 (el valor por defecto) solo actualizará el registro actual. Usando 1 o 2 actualizará todos los registros modificados. La diferencia entre 1 y 2 tiene que ver en que sucede si uno o más de los registros modificados no pueden ser actualizados.
Poniendo 1originará que el tableupdate falle si cualquiera de los registros no puede ser actualizado. Mientras que con 2 forzará a que Visual FoxPro actualice todos los registros que pueda y que guarde una lista de los registros que no pueden ser actualizados (esta lista es almacenada en la matriz pasada como cuarto argumento).
lForzar
Este argumento, de tipo lógico, controla si Visual FoxPro dará un fallo en el TableUpdate si el registro que está siendo actualizado ha sido cambiado por otro usuario mientras estabamos realizando las modificaciones. Un valor de .T. fuerza la actualización aunque haya un conflicto, mientras que .F. provocará que la actualización falle si el registro del disco no está igual que como estaba al principio de las modificaciones.
Recomiendo encarecidamente que este argumento esté siempre a .F.. La razón es que si no lo haces, tu código puede reemplazar las modificaciones de otros usuarios indiscriminadamente. Lo que realmente debes hacer es controlar el valor de retorno del TableUpdate para ver si se ha realizado correctamente o no, y responder en este caso.
cAliasTabla | nÁreaTrabajo
Este argumento es el nombre del área de trabajo al cual le afecta el tableupdate. Puede ser o bien el alias ó el número del área de trabajo (¿Sigue alguien usando números de áreas de trabajo en su código?).
Este es otro de los argumentos opcionales que es recomendado usar siempre. Si se omite este argumento entonces el TableUpdate afectará solo al área de trabajo activa. Como podemos controlar esta situación, es recomendable que le digamos al TableUpdate a que área de trabajo debe afectar y no depender que el área de trabajo haya sido correctamente seleccionada. La razón es que Visual FoxPro está orientado a eventos y puede llegar a suceder que no seas capaz de saber cual es el área de trabajo activa debido a que diversos eventos pueden cambiar el área de trabajo activa. Poniendo el alias en la función TableUpdate eliminarás cualquier posible efecto de equivocarte en el área de trabajo a actualizar.
cMatrizErrores
Este último argumento solo es válido si el primer argumento es 2. Con el primer argumento puesto a 2, la función TableUpdate intentará actualizar todos los registros modificados y si alguno falla se pondrá el número de registro que ha fallado en la matriz cMatrizErrores.
Un ejemplo de valores 1 y 2 como primer argumento pueden ayudar a saber que significa todo esto. Vamos a suponer que los registros 1, 3, 5, 6 y 9 han sido modificados en un bufer por tablas. Realizamos un TableUpdate con 1 ó 2 como primer argumento y hay un conflicto de actualización en los registros 3 y 6.
Con un valor de 1 en el primer argumento, la función TableUpdate actualizará el registro 1 y entonces cuando no pueda actualizar el registro 3 parará de intentar actualizar registros y devolverá un .F. indicando un fallo de la operación de actualización. Usar el valor 2 como primer argumento, provocará que la función TableUpdate continúe intentando actualizar el resto de los registros después de que fallara el registro 3. La matriz definida en el cuarto argumento mantendrá la lista de registros que no han podido ser actualizados (3 y 6), el TableUpdate de todos modos devolverá .F. indicando un fallo en la actualización debido a que no se ha completado dicha actualización.
…No Actualizar
Utilizar el almacenamiento de datos en bufer no solo nos permite guardar las modificaciones que ha realizado un usuario, también ofrece un método para descartar las modificaciones. La función TableRevert restaurará un bufer sucio poniendo los valores como estaban almacenados anteriormente en el disco, la sintaxis es la siguiente;
TABLEREVERT([lTodasFilas [, cAliasTabla | nÁreaTrabajo]])Los dos argumentos de esta función son descritos en las siguientes secciones.
lTodasFilas
Este argumento, de tipo lógico, controla que registros serán restaurados, si es .T. entonces todos los registros sucios serán restaurados en el alias seleccionado, un valor de .F., solo restaurará el registro actual. Si no se especifica este argumento solo afectará al registro actual.
cAliasTabla | nÁreaTrabajo
Es el nombre de alias o número de área de trabajo sobre la cual la función TableRevert actúa. Si no se especifica se hará sobre el área de trabajo activa.
¿Cómo es el vampiro asesino?
Las funciones TableUpdate y TableRevert devuelven valores que indican si han sido realizadas correctamente o con fallos. TableUpdate devuelve un valor lógico .T. indicando que la actualización ha sido completada satisfactoriamente o una valor .F. indicando que la actualización ha fallado en parte o completamente. TableRevert devuelve un número que indica el número de registros que han sido restaurados.
En ambos casos es importante conocer los valores devueltos y reaccionar a ellos. Por ejemplo, el siguiente código podría ser usado para actualizar un bufer de la tabla clientes.
IF NOT TableUpdate(1,.F.,”Clientes”)
Wait Window “Imposible grabar su trabajo”TableRevert(.T.,”Clientes”)ENDIFLa sentencia IF comprueba el valor devuelto por el TableUpdate y actúa en consecuencia. El TableRevert es necesario porque un TableUpdate que falle no actualiza la tabla, pero deja el bufer sucio y este debe ser limpiado antes de cerrar la tabla. Hay otras maneras de controlar fallos del TableUpdate, por ejemplo;
IF NOT TableUpdate(1,.F.,”Clientes”)
IF MessageBox(“Imposible grabar su trabajo. ¿Quieres descartar los cambios?”,;MB_YESNO,”Actualización fallida”) = IDYESTableRevert(.T.,”Clientes”)ENDIFENDIFEn este caso el fallo del tableupdate originará que el usuario reciba un message box preguntándole si desea descartar los cambios. Si se escoge "si" entonces la tabla es restaurada, si no se mantendrá en el formulario manteniendo sus cambios intactos. El usuario podrá intentar guardar los cambios mas tarde.
Téngase en cuenta en ambos ejemplos que no se ha puesto una selección del área de trabajo. Esto es debido a que el área de trabajo es especificada en las funciones y así no hay necesidad de seleccionar ningún área de trabajo en particular.
Resumen
En el ultimo artículo hemos revisado el sistema de almacenamiento de tablas en bufer a grandes rasgos y descubrimos lo que puede hacer por nosotros. Vimos que el almacenamiento en bufer puede hacer que nuestro código sea mas simple y fácil de entender
En este artículo han sido revisadas las dos principales funciones que afectan al almacenamiento en bufer, TableUpdate() y TableRevert(). Estas dos funciones nos dan un control definitivo acerca de si las modificaciones en el bufer serán escritas en el disco y cuando serán realizadas.
Vea también
Descarga
Buffering, El Vampiro asesino - Este documento
Referencias
Artículo original: Multi-User and Data Buffering Issues
Autor: Jim Booth
Traducido por: Pablo Roca