28 de julio de 2015

Utilizar un Select seguro para preservar los grids

Artículo original: Using a "Safe Select" to preserve your grid
http://weblogs.foxite.com/andykramek/2005/03/19/using-a-safe-select-to-preserve-your-grid
Autor:Andy Kramek
Traducido por: Ana María Bisbé York


Un problema común al trabajar con cursores locales en VFP es que cuando ellos se utilizan como origen para controles Grid, la recreación del cursor provoca que el grid pierda parte o toda su configuración. La razón por la que esto ocurre es que cada vez que se ejecuta una sentencia SQL en VFP, el cursor destino existente se cierra, se elimina y se recrea. Como consecuencia, los datos del cursor se pierden y el grid pierde su enlace.

Esto es fácilmente demostrable, considerando lo siguiente:

*** Ejecutar un select básico a un cursor
SELECT * FROM account INTO CURSOR JUNK NOFILTER
? DBF('junk') && Returns F:\TEMP\00002PL5008K.TMP 
*** Repetir la consulta
SELECT * FROM account INTO CURSOR JUNK NOFILTER
? DBF('junk') && Returns F:\TEMP\00002PL500AQ.TMP

Como puede ver, la segunda ejecución de la consulta crea un cursor nuevo que se abre con el mismo alias que el primero. Una comprobación rápida al disco mostrará que el primer cursor ha sido efectivamente eliminado. La consecuencia para el grid es que tiene que reconstruirse por si mismo desde el principio. Entonces, elimina todas las columnas existentes y crea nuevas columnas para el nuevo origen de datos. El resultado es, por supuesto, que todas las configuraciones específicas de las columnas se pierden y si el grid emplea columnas personalizadas o los controles han sido reemplazados por clases propias (o, desde la introducción de las clases member, en cualquier lugar que exista una clase member.) Por tanto, cualquier configuración específica se pierde y tiene que ser re-creada. ¡Esto, no es bueno!

Una solución al problema, que ofrezco con frecuencia en los foros on-line, es establecer la propiedad RecordSource del grid a una cadena vacía antes de hacer la consulta y resturarlo luego de realizada, algo así:

*** Guardar el contenido de la propiedad RecordSource y limpiarla
WITH ThisForm.Grid
  lcBoundTo = .RecordSource
  .RecordSource = ''
  *** Ejecutar la nueva consulta
  SELECT <fields> FROM <table> INTO CURSOR <recordsource>
  .RecordSource = lcBoundTo
ENDWITH

y esto trabaja bien, permitiendo que el grid muestre las columnas en el orden natural.

La razón para esta salvedad es que lo que ocurre en realidad en este caso es que al limpiar la propiedad RecordSource del grid, se limpia también la propiedad ControlSource para cada columna. Cuando el grid es re-enlazado a un alias nuevo no tiene especificado el ControlSource de cada columna y el resultado es que el grid muestra ahora los datos basados en la posición del RecordSource. La primera columna muestra la primera columna del origen de datos, la segunda columna muestra la 2da y así sucesivamente.

Generalmente lo que sucede a continuación es que la persona que ha preguntado, trata la solución ofrecida, encuentra que su grid está completamente revuelto y regresa al foro preguntando ¿y ahora que hago? El siguiente paso suele ser sugerirle que cambie enteramente su metodología y que sustituya el cursor por una vista parametrizada. Esto es perfectamente válido y una vista parametrizada trabajará muy bien para estos casos.

Sin embargo, las vistas parametrizadas tienen una seria limitación. Debido a que las vistas deben estar pre-definidas, no es fácil crear una vista que acepte una condición de filtro para un propósito específico y es precisamente este requerimiento el que lleva a los desarrolladores a emplear, en primer lugar, un cursor. Por ejemplo, en una pantalla de registro de clientes el requerimiento es permitir al usuario que especifique cualquier combinación de First Name, LastName, Social Security Number, City, State, Order Number. Construir una vista parametrizada para controlarlo sería difícil, sino imposible.

Una solución para este problema es la técnica conocida como "select seguro".

La idea, es que debido a que los cursores se crean siempre en la estación de trabajo local del usuario, y se abren siempre en exclusivo, podemos utilizar el comando ZAP para limpiar, sin cerrar, un cursor. Entonces, en lugar de correr una consulta directamente sobre un cursor de trabajo emplearemos un cursor intermedio (o "fantasma") como destino para la consulta y agregamos simplemente el resultado al cursor de trabajo. He aquí el código para el "select seguro".

*** Crear un cursor de trabajo
SELECT * FROM account WHERE 0 = 1 INTO CURSOR curacct READWRITE
*** Ahora ejecutar la consulta real
SELECT * FROM account WHERE name LIKE 'Sm%' INTO CURSOR curdummy
*** Limpiar el cursor de trabajo y agregar el resultado.
SELECT curacct
ZAP IN curacct
APPEND FROM DBF('curdummy')
USE IN curDummy

Debido a que el cursor de trabajo no se cierra nunca, no hay ningún efecto sobre ningún control (incluyendo grids) que utilice el cursor como origen de datos. Esta técnica nos permite mantener la flexibilidad de un cursor sin los efectos secundarios indeseados causados por las repetidas aperturas y cierres de las re-consultas. Por supuesto, si tiene que escribir este código cada vez que desea re-crear un cursor podría ser bastante tedioso; pero tenemos un entorno completamente orientado a objeto en VFP, porqué no agregar un método a  su formulario base que acepte dos parámetros - la cadena para la consulta y el nombre del cursor destino. Entonces, el código queda bien encapsulado y está disponible en cualquier forma que lo necesitemos. Puede incluso completar el código de tal forma que si el cursor destino no existe, sea creado por este método. He aquí mi método base "SaveSelect" con el que espero recibir una cadena de consulta que NO incluya una cláusula INTO - esto se agrega aquí:

LPARAMETERS tcSql, tcAlias
LOCAL lnSelect, lcSql
*** Guardar el área de trabajo
lnSelect = SELECT(0)
*** No existe el cursor
IF NOT USED( tcAlias )
  *** Crearlo directamente
  lcSql = tcSql + " INTO CURSOR " + tcAlias + " READWRITE"
  &lcSql
ELSE
  *** El cursor existe, utilizo un select seguro
  lcSql = tcSql + " INTO CURSOR curdummy"
  &lcSql
  *** Limpiar y actualizar el cursor de trabajo
  SELECT (tcAlias)
  ZAP IN (tcAlias)
  APPEND FROM DBF('curdummy')
  USE IN curdummy 
ENDIF
*** Restablecer el área de trabajo y devolver el estado
SELECT (lnSelect)
RETURN USED(tcAlias)

Y sobre la cláusula READWRITE, si aun está utilizando una versión que no la soporte, existe una forma para hacerlo; pero  no lo diré porque se debe haber Actualizado. No hay ninguna razón para no haberlo hecho y sí muchísimas por las que debería - la principal es animar a Microsoft para que continúe desarrollando VFP, por ejemplo ...

2 comentarios :

  1. Uma forma fácil de fazer isso e sem muito código.

    THISFORM.grdTeste.RecordSource = ""
    CREATE CURSOR curTeste (Nome C(50)
    THISFORM.grdTeste.RecordSource = "curTeste"

    Sempre que for criar o novo cursor execute este comando antes de criá-lo.
    THISFORM.grdTeste.RecordSource = ""

    Com isso não ira perder a configuração (Design) da grade feita.

    ResponderBorrar
  2. En una programación en capas tengo un prg inicial que llama al formulario pero antes de llamarlo creo el cursor:
    oApp.oClientes.SolicitaCursorListaCliente()
    Do form fClientesMain
    oApp.oClientes.SolicitaDestruirCursorListaCliente()

    ResponderBorrar

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