13 de noviembre de 2012

Migrar Vistas Remotas a CursorAdapter

Para los que hemos desarrollado aplicaciones Cliente-Servidor, con nuestro querido Zorro, el uso de la tecnología de Vistas Remotas ha sido sin duda la primera opción. Bajo esta suposición, puede que nos hayamos sentido a gusto desarrollando aplicaciones hasta la versión 7, de allí en adelante sin embargo, VFP incorporó otros cambios sustanciales y específicamente en la administración y gestión de datos y que podríamos usar como alternativa: La tecnología CursorAdapter. Ahora toca contarles por que decidí cambiarme a esta última.

Una de las mayores dificultades que encontré en la versión 8 y 9 al trabajar con Vistas Remotas , fue la pésima gestión que tiene VFP al momento de gestionar la concurrencia y las conexiones en el servidor, hablando un poco más claro: Me refiero a que la propiedad Shareconection de las vistas remotas. Esta propiedad controla la apertura de una nueva conexión cada que se abre una vista remota en una determinada sesión de datos. Es decir si esta propiedad se establece como verdadera (.T.) , nos aseguraremos de que al abrir el primer cursor o vista remota generaremos un numero de conexión determinado para ella, y las vistas que se abran posteriormente compartirán este mismo hilo de conexión (Siempre y cuando que estas tengan la propiedad Shareconecction establecida a TRUE) , de esta manera tendremos varias vistas abiertas con una sola conexión. Al hacer esto tenemos asegurado lo siguiente:
  • Manejo eficiente de las conexiones al servidor
  • Mejor manejo de las Transacciones, en especial al actualizar las vistas con las funciones TABLEUPDATE(), SQLCOMMIT() y SQLROLLBACK()
  • Mejor administración de código
La mala noticia es que en la versión 9, no respeta el valor de la propiedad ShareConection, por lo que se abrirán tantas conexiones como vistas se abran.

Por esta razón, decido cambiarme a la tecnología CursorAdapter , pero esto significa cambiar algunas cosas en el código de las aplicaciones que hayamos escrito anteriormente, y ese el principal motivo de este artículo. En primer lugar debemos de tener en cuenta que CursorAdapter Tiene las propiedades y comportamientos que cualquier cursor o vista remota y aun más todavía. De modo que si logramos abrir Cursores derivados de CursorAdapter con las mismas propiedades que nuestras vistas en el entorno de datos, no tendremos que cambiar nuestro código. Tendremos que preocuparnos entonces de crear y configurar los objetos CursorAdapter de tal manera que se comporten tal y como nuestras vistas remotas, el mejor lugar y el momento de hacerlo para cumplir el objetivo propuesto es sin dudas el evento BeforeOpenTables del Entorno de datos de nuestro formulario:

En primer lugar vamos a definir una clase heredada de la Clase CursorAdapter para inicializar algunas propiedades y agregar unos métodos a nuestra conveniencia. Lo podemos hacer dentro de una archivo Prg.
DEFINE CLASS CursorVista AS CURSORADAPTER

  && Estas 2 siguientes propiedades pueden establecerse
  && dinamicamente de acuerdo a las
  && Técnicas que el programador este usando

  DATASOURCETYPE="ODBC"
  DATASOURCE=SQLSTRINGCONNECT("DSN=PostgreSQL31W;DATABASE=guia;SERVER=localhost;PORT=5432;UID=miusuario;PWD=miclave" + ;
    ";CA=d;A6=;A7=100;A8=4096;B0=255;B1=8190;BI=0;C2=dd_;CX=1b502bb;A1=7.4-1")
  ALLOWDELETE = .T.
  ALLOWINSERT = .T.
  ALLOWUPDATE = .T.
  NODATA = .T.
  PREPARED = .T.
  SENDUPDATES = .T.
  WHERETYPE = 1

  && Este método toma como parámetro el nombre de la base de datos y el
  && nombre de la vista que está contenida en la misma, luego clona
  && todos las propiedades de la vista y las traslada al Objeto Cursor
  && Adapter Correspondiente

  PROCEDURE atrapavista
    LPARAMETERS Pnombredb,Pnombrevista
    && veriifcando que este abierta la base de datos
    IF OCCURS(ALLTRIM(UPPER(PnombreDB)) , ALLTRIM(UPPER(DBC()))) > 0 THEN
      THIS.ALIAS=Pnombrevista
      THIS.SELECTCMD=DBGETPROP(Pnombrevista,"VIEW","SQL")
      THIS.TABLES=DBGETPROP(Pnombrevista,"VIEW","Tables")
      DIMENSION papedazos(1)
      THIS.explotacadena(',',ALLTRIM(UPPER(STREXTRACT(THIS.SELECTCMD,'SELECT ',' FROM '))),@papedazos)
      FOR K=1 TO ALEN(papedazos)
        STORE THIS.aclaracampo(papedazos(k)) TO papedazos(k)
        THIS.CURSORSCHEMA=THIS.CURSORSCHEMA+papedazos(k)+SPACE(2)+DBGETPROP(Pnombrevista+'.'+papedazos(k),"FIELD","DataType")+ ','
        THIS.UPDATENAMELIST=THIS.UPDATENAMELIST+IIF(DBGETPROP(Pnombrevista+'.'+papedazos(k),"FIELD","Updatable"),papedazos(k)+ ;
          SPACE(2)+DBGETPROP(Pnombrevista+'.'+papedazos(k),"FIELD","Updatename")+',',"")
        THIS.UPDATABLEFIELDLIST=THIS.UPDATABLEFIELDLIST+IIF(DBGETPROP(Pnombrevista+'.'+Papedazos(k),"FIELD","Updatable"),Papedazos(k)+',',"")
        THIS.KEYFIELDLIST=THIS.KEYFIELDLIST+IIF(DBGETPROP(Pnombrevista+'.'+papedazos(k),"FIELD","KeyField"),Papedazos(k)+',',"")
      ENDFOR
      THIS.CURSORSCHEMA=SUBSTR( THIS.CURSORSCHEMA,1,LEN(THIS.CURSORSCHEMA)-1)
      THIS.UPDATABLEFIELDLIST=SUBSTR(THIS.UPDATABLEFIELDLIST,1,LEN(THIS.UPDATABLEFIELDLIST)-1)
      THIS.KEYFIELDLIST=SUBSTR(THIS.KEYFIELDLIST,1, LEN(THIS.KEYFIELDLIST)-1)
      RETURN 1
    ELSE
      MESSAGEBOX("No esta abierta la base de datos ","")
    ENDIF
  ENDPROC

  PROCEDURE explotacadena
    LPARAMETERS PCARACTER, PCADENA, apedazos
    PCARACTER=ALLTRIM(UPPER(PCARACTER))
    PCADENA=ALLTRIM(UPPER(PCADENA))
    LOCAL NCANTIDADEVECES

    NCANTIDADEVECES=OCCURS(PCARACTER,PCADENA)
    DIMENSION APEDAZOS  (ncantidadeveces+1)

    FOR I=1 TO NCANTIDADEVECES+1
      IF I=1
        pedazo=SUBSTR(PCADENA,1,AT(PCARACTER,PCADENA,I)-1)
      ELSE
        IF I=NCANTIDADEVECES +1
          pedazo=SUBSTR(PCADENA,RAT(PCARACTER,PCADENA,1)+1)
        ELSE
          pedazo=SUBSTR(PCADENA,AT(PCARACTER,PCADENA,I-1)+1,AT(PCARACTER,PCADENA,I)-AT(PCARACTER,PCADENA,I-1)-1)
        ENDIF
      ENDIF
      STORE pedazo TO Apedazos(i)
    ENDFOR
    RETURN @APEDAZOS
  ENDPROC

  PROTECTED PROCEDURE aclaracampo
    LPARAMETERS Pnombrecampo
    LOCAL auxcampo AS STRING
    PNOMBRECAMPO=ALLTRIM(UPPER(PNOMBRECAMPO))
    IF AT(' AS ',pnombrecampo) > 0 THEN
      IF AT(' AS ',pnombrecampo,2) > 0 THEN
        auxcampo=STREXTRACT(pnombrecampo,' AS ','',2)
      ELSE
        auxcampo=STREXTRACT(pnombrecampo,' AS ','')
      ENDIF
    ELSE
      IF AT('.',pnombrecampo) > 0 THEN
        auxcampo=STREXTRACT(pnombrecampo,'.','')
      ELSE
        auxcampo=pnombrecampo
      ENDIF
    ENDIF
    RETURN AUXCAMPO
  ENDPROC

ENDDEFINE
Nuestro formulario de ejemplo simula una guía de remisión electrónica, con datos del encabezado y una grilla con el detalle de los ítems del documento usaba originalmente 4 vistas remotas de las cuales:

Vw_guia: Vista con los datos del encabezado, con campos de varias tablas remotas, es actualizable y esta parametrizada, almacenamiento de optimista de fila. (Buffering=3)
VW_detgui: Detalle del documento, actualizable y parametrizada, almacenamiento optimista de tabla (Buffering=5)
Vw_embarcaciones: Vista no parametrizada y abre datos maestros de una tabla remota. No es actualizable
vw_documentos_correo: Vista parametrizada que contiene datos maestros de una tabla remota, no es actualizable.

Luego en el evento BeforeOpenTables del entorno de datos del formulario, debemos de crear los objetos y agregarlos como cursores miembros, de la siguiente manera según las características de cada vista:
&& Objeto que clona a la vista vw_guia
guia=CREATEOBJECT("CursorVista")
THIS.ADDOBJECT("guia","CursorVista")
THIS.guia.atrapavista("geus","vw_guia")
THIS.guia.ALIAS="vw_guia"
THIS.guia.NODATA=.T.
IF !THIS.guia.CURSORFILL()
  AERROR(nn)
  MESSAGEBOX(nn(1,2),"")
ELSE
  * this.guia.cursorrefresh()
ENDIF

&& Objeto que clona a la vista vw_detgui
detalle=CREATEOBJECT("CursorVista")
THIS.ADDOBJECT("detalle","CursorVista")
THIS.detalle.NODATA=.T.
THIS.detalle.atrapavista("geus","vw_detgui")
THIS.detalle.ALIAS="vw_detgui"
THIS.DETALLE.BUFFERMODEOVERRIDE=5 &&Almacenamiento de tabla
IF !THIS.detalle.CURSORFILL()
  AERROR(nn)
  MESSAGEBOX(nn(1,2),"")
ELSE
  *this.detalle.cursorrefresh()
ENDIF

&& Objeto que clona a la vista vw_embarcaciones
embarcaciones=CREATEOBJECT("CursorVista")
THIS.ADDOBJECT("embarcaciones","CursorVista")
THIS.embarcaciones.NODATA=.F. &&No esta parametrizada, cargar los datos sin mas
THIS.embarcaciones.atrapavista("geus","vw_embarcaciones")
THIS.embarcaciones.ALIAS="vw_embarcaciones"
IF !THIS.embarcaciones.CURSORFILL()
  AERROR(nn)
  MESSAGEBOX(nn(1,2),"")
ELSE
  THIS.embarcaciones.CURSORREFRESH() &&Cargar los datos sin mas
ENDIF

&& Objeto que clona a la vista vw_documentos_correo
documentos_correo=CREATEOBJECT("CursorVista")
THIS.ADDOBJECT("documentos_correo","CursorVista")
THIS.documentos_correo.atrapavista("geus","vw_documentos_correo")
THIS.documentos_correo.ALIAS="vw_documentos_correo"
IF !THIS.documentos_correo.CURSORFILL()
  AERROR(nn)
  MESSAGEBOX(nn(1,2),"")
ELSE
  THIS.documentos_correo.NODATA=.T.
  *this.documentos_correo.cursorrefresh()
ENDIF
En los dos primeros casos como ambos Objetos se derivan de vistas parametrizadas, se configura la propiedad NoData a FALSE con lo que nos aseguramos que el método CursorFill() no desencadene el cuadro de dialogo solicitando el valor del parámetro, en el tercer caso a diferencia de los anteriores al tratarse de una vista sin parámetros se cargan los datos sin restricciones, en el cuarto caso al igual que los dos primeros el método CursosFill() solo abrirá el cursor vacio en la sesión, quedando a nuestra cuenta invocar a los datos posteriormente con el método CursorRefresh(), esto puede hacerse en el evento Init o load del formulario en cuestión suministrando para ello el valor del parámetro que filtrara los datos.

A partir de entonces ya se puede trabajar con los Cursores CursorAdapter como si se trataran de Vistas Remotas, y lo mejor de todo esto es que solo se ha abierto un solo hilo de conexión.

Julián [HIPOGEA]

No hay comentarios. :

Publicar un comentario