Artículo original: Handling buffered data in Visual FoxPro
http://weblogs.foxite.com/andykramek/2005/12/27/handling-buffered-data-in-visual-foxpro
Autor: Andy Kramek
Traducido por: Ana María Bisbé York
Un par de meses atrás escribí sobre buffer; pero deliberadamente dejé fuera de la discusión dos funciones vitales mientras trabajaba con buffer- nombradas TableUpdate() y TableRevert(). Estas son la base mediante la cual usted, el desarrollador, controla la transferencia de los datos entre el buffer de Visual FoxPro y el origen de datos originales. La función TableUpdate() toma los datos pendientes desde el buffer y los actualiza en la tabla original, mientras TableRevert() refresca los buffers para releer el dato desde el origen de datos. La realización exitosa de otras funciones da como resultado un buffer 'limpio' lo que significa que, para Visual FoxPro, los buffer y el origen de datos son sincronizados.
Controlar el alcance de las actualizaciones
Debido a que Visual FoxPro admite tanto buffer de Filas y de Tablas, ambas funciones de traspaso de datos pueden operar "sólo sobre el registro actual" o sobre "todos los registros modificados". El primer parámetro que se pasa a la función determina el alcance de la operación y ufff tenemos otra confusión posible aquí. Las funciones operan en modo significativamente diferente y tienen tipos diferentes de valores devueltos.
En la versión 3.0 de Visual FoxPro; ambas TableUpdate() y TableRevert() aceptarían solamente un parámetro lógico para determinar el alcance del cambio que están controlando. Pasar un valor de .T., significa que todos los cambios pendientes fueron procesados, mientras .F. restringe las operaciones al registro actual solamente, sin importar el modo de buffer empleado.
TableRevert() devuelve el número de filas que fueron revertidos y no pueden realmente "fallar" - sin que fuera un problema físico, como perdiendo una conexión de red. En una fila de tabla en buffer, o cuando específicamente el alcance como .F., el valor devuelto, por tanto siempre 1.
TableUpdate() devuelve un valor lógico que indica si la modificación especificada es exitosa, independientemente del alcance. En otras palabras, un valor devuelto de .T., indica que todos los registros en el alcance han sido actualizados con éxito. Sin embargo, el comportamiento cuando se utiliza un parámetro lógico para determinar el alcance y la actualización falla por cualquier razón, es que no se genera un error y la función devuelve un valor de .F. dejando el puntero en el registro en que falla.
Si está actualizando un solo registro, esto es muy sencillo; pero si está actualizando múltiples registros en una tabla, y un registro no puede ser actualizado, esto significa que cualquier actualización posterior no puede ser verificada. Pero, después de solucionar el conflicto para el registro que ha fallado, no hay garantía que re-intentar la actualización que no va a fallar al registro más cercano. Esto puede ser un problema!
El comportamiento de TableUpdate() fue, por tanto, modificado en la versión 5 para aceptar incluso un parámetro numérico o lógico para el alcance, donde 0 es equivalente para .F. y 1 para .T. El nuevo comportamiento, que puede ser solamente especificado al pasar "2" como parámetro de alcance, específicamente dirigidos los problemas de actualización de múltiples registros.
Al utilizar buffer de tablas, al llamar TableUpdate() con un parámetro de alcance de "2" Visual FoxPro intenta actualizar todos los registros que tienen cambios pendientes. Sin embargo, si un registro no puede ser actualizado, en lugar de parar, la función registra el número de registro que ha fallado en una matriz (cuyo nombre puede ser especificado como el cuarto parámetro) y continúa tratando de actualizar otros registros cambiados. La función devuelve .F. si cualquier registro falla la actualización; pero al menos tratará de actualizar todos los registros disponibles. La matriz de salida contiene una lista de los números de registros para aquellos registros fallan la actualización.
El segundo parámetro para TableUpdate()
Una diferencia mayor entre la sintaxis de TableUpdate() y TableRevert() es que el anterior puede solamente tomar un parámetro extra lógico, en la segunda posición en la lista de parámetros. Esto controla la forma en la que se comportan las actualizaciones cuando encuentran un conflicto.
De forma predeterminada, Visual FoxPro debe rechazar un cambio cuando un conflicto entre el buffer de datos y se detecta el dato original (vea debajo un debate completo del conflicto y la resolución). Al especificar un valor lógico .T. como el segundo parámetro puede forzar una actualización a que sea aceptada incluso en situaciones cuando debería fallar. Naturalmente esto es algo que deseará hacer de forma predeterminada; pero existen, como veremos luego, situaciones donde este comportamiento no es solamente deseado, sino esencial.
Especificar la tabla a ser actualizada o revertida
Ambas funciones TableUpdate() y TableRevert() operan sobre una tabla al mismo tiempo. El comportamiento predeterminado es que, a menos que específicamente, se determine lo contrario; actuará en la tabla en el área de trabajo seleccionada. Si no hay una tabla abierta en el área de trabajo, hay un error (No se encuentra el alias - Error 13). Ambos, sin embargo, pueden actuar sobre una tabla abierta disponible en la sesión de datos actual y puede aceptar cualquiera de los nombres de ALIAS (el tercer parámetro para TableUpdate(), segundo para TableRevert()) o un número de área de trabajo.
No recomendamos el uso del número del área de trabajo en esto, o cualquiera, situación donde está especificando una tabla diferente que la seleccionada. Tal y como hemos podido ver esta funcionalidad está incluida, sólo por compatibilidad hacia atrás y no tiene lugar en el entorno VFP. Existen dos razones para evitar el uso del área de trabajo. Primeramente, hace su código dependiente de tablas específicas estén abiertas en áreas específicas de trabajo - lo que es aun mayor limitación si piensa cambiar! En segundo lugar, no tiene control real sobre las tablas abiertas en VFP, los cursores o vistas cuando utiliza de cualquier forma el Entorno de datos del formulario. Entonces, liberar el número del área de trabajo en lugar del alias, es una estrategia muy arriesgada e innecesaria.
El único momento que recomendamos el uso del número de área de trabajo es cuando guardamos el área de trabajo actual guardando el valor devuelto de la función SELECT(0). Utilizar el número del área de trabajo en este caso asegura que el área de trabajo actual está vacía, o que las tablas que contienen se cierren durante cualquier operación que esté haciendo, puede aun devolverlo sin error.
Conclusión
Existe mucha funcionalidad y flexibilidad oculta dentro de TableUpdate() y TableRevert(). Al utilizar buffer, necesita tener cuidado de, exactamente cuáles de varias combinaciones de sus parámetros, puede pasarles para asegurar que está utilizando la combinación correcta que necesita. Mientras TableRevert() es bastante simple, TableUpdate() es más compleja y por eso, en la Tabla 2 que muestro debajo se brinda un resumen de algunas combinaciones "prácticas" de parámetros para TableUpdate().
Tabla 2. Opciones de TableUpdate()
Parámetros
Alcance | Fuerza | Tabla | Salida | Acción |
---|
0 ó .F. | .F. | | | Intenta actualizar la fila actual del alias actual |
0 ó .F. | .T. | | | Fuerza la actualización del registro actual solamente del alias actual |
0 ó .F. | .F./.T. | Alias | | Intenta forzar todo los registros disponibles de las alias especificadas |
1 ó .T. | .F. | | | Intenta actualizar la fila actual sólo del alias especificado |
1 ó .T. | .T. | | | Fuerza la actualización de todos los registros disponibles del alias actual |
1 ó .T. | .F./.T. | Alias | | Intenta actualizar la fila actual sólo del alias especificado. Se detiene en un fallo |
2 | .F. | Alias | Matriz | Intenta actualizar todos los registros disponibles del alias especificado. Nota los fallos; pero no se detiene. |
2 | .T. | | | Fuerza la actualización de todos los registros disponibles del alias actual/especificado |
2 | .F./.T. | Alias | Matriz | Intenta/Fuerza la actualización de todos los registros disponibles del alias especificado. Nota los fallos; pero no se detiene. |
Detectar y solucionar conflictos
La sección anterior trataba sobre los mecanismos para actualizar una tabla con buffer y en mi artículo anterior, recomendé que el Buffer de Tabla optimista debe ser la opción normal para la mayoría de las aplicaciones. Entonces, el próximo problema está, en asegurarse de que los cambios no son la causa de un conflicto de actualización cuando guarda. Uno de los problemas inherentes al utilizar bloqueo optimista en un entorno multiusuario es que es posible para más de un usuario hacer cambios al mismo registro al mismo tiempo. Puede preguntarse ¿porqué este tipo de cosa pudiera ser posible alguna vez?- ¿ está seguro de que dos usuarios no pudieran actualizar el mismo registro al mismo tiempo?
En la práctica, existen muchas situaciones donde puede ocurrir legítimamente. Considere la situación en una Orden de Ventas en el procesamiento del sistema. Cuando se coloca una orden para un elemento, el "stock disponible" actual debe reajustarse para reflejar la reducción. Si dos usuarios, que son controlados por dos operadores simultáneamente, incluyen el mismo elemento en sus órdenes, existe un gran posibilidad de que surja un conflicto. Obviamente, esto puede no ocurrir si el sistema utiliza bloqueo pesimista; pero esto tiene otras consecuencias, generalmente indeseables. En este caso, el segundo operador, que trata de acceder al elemento en cuestión, recibirá un mensaje que el registro está en uso por alguien y no puede hacer modificaciones - no es mucha ayuda ! Aún más, el bloqueo pesimista puede ser utilizado cuando una tabla Visual FoxPro es utilizada directamente como origen de dato - no puede bloquear de forma pesimista una vista de cualquier tipo (ni para datos locales o remotos).
Al utilizar buffer, Visual FoxPro hace una copia de todos los datos como e recuperan de la tabla física, cuando se pide una actualización, se compara esa copia con el estado actual del dato. Si no hay cambios, la actualización es permitida, en otro caso, la actualización se genera un error de conflicto (#1585 para las vistas, #1595 para tablas).
El rol de OldVal() y CurVal()
La base de toda la detección del conflicto está en dos funciones nativas, OldVal() y CurVal(), las que acceden a los cursores intermedios creados por Visual FoxPro cuando utiliza datos en buffer. Como indican sus nombres, OldVal() devuelve el valor de un campo tal y como era cuando el usuario final leyó el dato del origen, mientras CURVAL() devuelve el estado actual del dato en la tabla origen. Estas dos funciones operan desde el nivel de campo y, aunque ambas pueden aceptar una expresión que evalúa una lista de campos, son más usadas para devolver valores de campos individualmente para evitar el problema de solucionar diferentes tipos de datos.
Aquí hay un problema al utilizar CurVal() para verificar el estado de la vista. Visual FoxPro realmente mantiene un nivel adicional de buffer para una vista, la cual, a menos que sea refrescada inmediatamente antes de verificar el valor del campo, puede causar CurVal() que devuelve la respuesta incorrecta.
Entonces, ¿Cómo puedo realmente detectar el conflicto?
Antes de entrar en la discusión de cómo detectar un conflicto, permítanme ser claro sobre la definición de Visual FoxPro sobre lo que es un conflicto. Como he comentado antes, en la discusión sobre buffering, Visual FoxPro realmente hace dos copias de un registro, cada vez que el usuario accede al dato. Una copia está disponible como cursor modificable y es donde un usuario hace los cambios. La otra copia guarda el estado original. Antes de permitir la actualización, Visual FoxPro compara este cursor original con el dato guardado actualmente en el disco. Ocurre un conflicto cuando estas dos versiones del dato no coincide exactamente, y existen dos formas en las que puede ocurrir. La primera, y más obvia, es debido a que el usuario actual hace cambios a un registro y trata de guardar esos cambios después de que otro ha cambiado y guardado el mismo registro.
La segunda es menos obvia y ocurre cuando un usuario hace un cambio; pero entonces los cambios van a sus valores originales. El resultado es que no cambia cuando en realidad hace; pero cuando tratan de "guardar" el registro Visual FoxPro va a ver aun esto como un conflicto debido a que los valores OldVal() y CurVal(), en realidad son diferentes. Para evitar este tipo de error puede simplemente confiar en GetFldState(); pero debe comparar expresamente los valores en el buffer actual con estos en OldVal().
Entonces, habiendo evitado la posibilidad de conflictos cuando el usuario actual no ha hecho ningún cambio; pero sencillamente no trata de confirmar el registro, existen básicamente dos estrategias que puede adoptar para detectar conflictos. La primera es el proceder "Trate y vea". Esto significa simplemente que no trata y detecta conflictos potenciales; pero sólo atrapa el resultado de una llamada para TableUpdate() y cuando falla, determina porqué.
El segundo proceder es "Cinturones y tirantes" en el que cada campo cambiado se verifica individualmente y cualquier conflicto se soluciona antes de intentar actualizar la tabla original. Mientras esto parece más defensivo, y por tanto "mejor" en realidad oculta un problema. En el tiempo que esto ocurre (aunque sea muy pequeño) para verificar todos los cambios contra sus valores actuales, otro usuario puede cambiar exitosamente el mismo registro que está verificando. Entonces, a menos que también haya bloqueado explícitamente el registro antes de comenzar a verificar los valores, la actualización real pudiera fallar. Debido a que queremos realmente evitar explícitamente la colocación de bloqueos, necesita incorporar exactamente la misma verificación del resultado de TableUpdate(), y brinda el mismo control del fallo, lo que es mucho más simple precisamente la estrategia "Intenta y veremos".
Por tanto, a menos que tenga una razón para sobreescribir cambios pre-validados, recomendamos fuertemente que permita que Visual FoxPro detecte los conflictos y justamente atrápelo para aquellos errores que han surgido.
De acuerdo, entonces, habiendo detectado un conflicto de actualización, ¿qué puedo hacer sobre esto?
He aquí cuatro estrategias básicas para actualizar conflictos. Puede escoger una, o combine más de una en una aplicación en dependencia de la situación real:
[1] El usuario actual siempre gana - Esta estrategia es apropiada solamente en aquellas situaciones en las que se asume que son correctos los datos del usuario que está actualmente intentando guardar. Típicamente esto debería ser implementado en la base del ID del usuario, el que es en realidad está haciendo el guardado y debería implementar una regla de negocio que cierta información de una gente es más útil que otra.
Un ejemplo podría ser tener una aplicación donde un operador hable al usuario podría tener derechos de sobreescritura para contactar información para el cliente (en la base que la persona realmente habla al cliente es más probablemente capaz de obtener los detalles correctos). El conflicto puede surgir en este caso cuando un administrador está actualizando un detalle de cliente desde un dato en archivo o última orden, mientras un operador tiene detalles nuevos, directamente desde el cliente. La implementación de esta estrategia en Visual FoxPro es muy sencilla. Simplemente establezca el parámetro "FORCE", (el segundo) en la función TableUpdate() a ".T." y reintente la actualización.
[2] El usuario actual siempre pierde - Esto es exactamente lo contrario a la anterior. Al usuario actual se le permite solamente guardar los cambios siempre que no hay otro usuario que haya hecho cambios. Por el contrario, será implementado normalmente en base al ID del usuario y podría reflejar la probabilidad que este usuario en particular es propenso a trabajar con información "histórica" en lugar de información "actual". La implementación en Visual FoxPro es también muy sencilla. Los cambios del usuario actual se revierten, se recarga la información original y el usuario tiene que hacer cualquier cambio que necesite, una vez más. Esta es, probablemente la estrategia que es adoptada más frecuentemente - pero usualmente en base global.
[3] El usuario actual gana a veces - Esta estrategia es la más compleja de las cuatro a implementar; pero es en la actualidad, bastante frecuente. El principio básico es que cuando ocurre un conflicto de actualización, usted determina si alguno de los campos, que el usuario actual ha cambiado, van a afectar los cambios hechos por otro usuario. Si no, el registro del usuario actual es actualizado automáticamente (utilizando el valor de CURVAL()), entonces esto provoca que es negado el conflicto y la actualización se reintenta. Sin embargo, debido a no puede cambiar los valores devueltos por OldVal(), necesita forzar la segunda actualización.
Incidentalmente, esta estrategia también está dirigida al problema de cómo controlar el conflicto de actualización "falso positivo". Esto ocurre cuando existen discrepancias entre los valores del disco y aquellos en el buffer de usuario, pero los actuales cambios del usuario, no crean en realidad un conflicto con cualquier otro cambio que se haya hecho. Claramente, esta situación no es en realidad un conflicto; pero necesita ser controlada.
Aunque no es trivial, la implementación en Visual FoxPro es relativamente fácil. Primero, la función CURVAL() es utilizada para determinar cómo actualizar el buffer de usuario actual de tal forma que no va sobreescribir los cambios hechos por otro usuario. Entonces, la actualización se aplica utilizando el segundo parámetro FORCE en el TableUpdate() para decirle a Visual FoxPro que ignore el conflicto que van a surgir porque OldVal() y CurVal() no corresponden.
[4] El usuario actual decide - Este es el caso de "Coge todo". El conflicto no falla bajo ninguna regla de negocio reconocida, así que la única solución es preguntarle al usuario cuya acción de guardar desencadena el conflicto que es lo que desea hacer. La idea básica es que usted muestre al usuario que ha desencadenado el conflicto con una lista de valores - aquellos que acaba de entrar) y el valor que hay ahora en la tabla ( es decir con el cual alguien ha cambiado el valor original). El usuario puede entonces decidir si hay que forzar o revertir sus propios cambios. Las herramientas básicas para la implementación de esta estrategia han sido discutidos en secciones anteriores. Todo lo requerido es determinar qué cambios traen conflicto y los presentan al usuario como vía para que el usuario pueda decidir en un campo a campo qué hacer, en base al conflicto. En la práctica, esta estrategia en general se combina con la estrategia [3] mostrada antes, así al usuario sólo se le presenta una lista de campos donde hay un conflicto en los campos que han modificado por ellos mismos.
En el próximo artículo de esta serie, veré el diseño e implementación de una clase que controle un conflicto que puede ser arrastrada a un formulario.