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
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 ENDDEFINENuestro 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() ENDIFEn 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]