Documente Academic
Documente Profesional
Documente Cultură
Aunque nunca antes haya escrito una aplicacin de 3 capas en Foxpro, ha odo hablar
ciertamente de ellas. Cul es la gran idea? Es algo que necesitamos aprender?
Publicado por msalias el viernes, 12 de marzo de 2004
Por qu 3 capas?
Tres-Capas es una variante de n-Capas. A llama a B, B llama a C, etc. Cada una hace su
parte de la tarea. Con familias de servidores y aplicaciones ASP, pueden existir
diferentes capas de datos, una capa para generar pginas, y muchas ms. Para nuestros
propsitos, tres-capas es generalmente suficiente. En los diagramas de tres-capas
usuales (qu distribuiremos aqu), A es un formulario, B es una capa de acceso a datos,
y C es el lugar dnde guardamos los datos - normalmente DBFs en nuestro mundo, pero
eso est cambiando, y es ah donde entra nuestra capa de acceso a datos.
Existen numerosas ventajas. Cuando salvamos una Base de datos de SQL, estamos
guardando un solo archivo. La salva puede ejecutarse mientras los usuarios estn en el
sistema. Y restaurar es un comando de una lnea.
La seguridad es otro problema con las tablas de FoxPro. Cualquiera con acceso al
directorio de las DBFs en el servidor puede ver nuestras tablas. El Servidor de SQL, por
otro lado, tiene la seguridad incorporada. As que puede decidir quin lo hace. Cada da
que pasa el riesgo aumenta y los usuarios conocen y exigen mejoras de seguridad. El
servidor de SQL es una buena manera de lograrlo.
S su cliente vende. Instale el SQL. Puede ejecutar el SQL simplemente en su mquina
de desarrollo; de hecho, es una gran idea. Asegrese de instalar Developer Edition que
tiene una consola de administracin. Si todo lo que posee es MSDE, funcionar bien,
pero tendr que crear la base de datos, ndices e inicios de sesin programticamente, y
simplemente es un poco ms difcil aprender las tareas de SQL de forma no visual.
El Servidor de SQL corre como un servicio. "Escucha" las peticiones de las estaciones,
hace lo que se le pregunta, y enva el resultado de vuelta a la estacin. No hay trfico
del ndice porque los ndices no regresan con los resultados. La mayora del retraso que
podramos experimentar con aplicaciones de Foxpro sobre LAN es debido al trfico de
la red, resolver solamente el problema del retraso es una motivacin suficiente para
emigrar al Servidor de SQL.
Si ya hemos instalado el Servidor de SQL, necesitamos pasar la aplicacin para usar las
tablas del Servidor de SQL. Primero, tenemos que pasar los datos. Hay varias maneras
de hacer esto, y todas presentan problemas. Podemos usar el Asistente de Upsizing de
SQL para hacerlo, pero las tablas del SQL resultantes pueden causar dolores de cabeza
de programacin serios. Existe una utilidad de DTS que se instala cuando usted carga el
Servidor del SQL, y si le gusta escribir sus rutinas de recodificacin en BASIC,
conseguir lo correcto. Yo prefiero FoxPro. As que si su mejor apuesta es escribir su
propio programa de migracin de datos. No se preocupe - es incluido en este artculo.
Donde radican los errores con el Asistente de Upsizing? En primer lugar, tiene como
valor predefinido permitir NULLs. Si nunca ha odo hablar de NULLs, seguro no le van
a gustar. Estadsticos necesitan conocer si un valor de cero es un valor informado o es
simplemente alguien que no contest la pregunta - por ejemplo, su edad? no podemos
calcular la media de la edad sumando las edades y dividiendo por cero si la mitad del
encuestados no quiso contestar. As que tiene que saber qu son los "valores perdidos".
El Servidor del SQL permite los valores perdidos, y de hecho los valores
predeterminados a ellos. Pero ellos casi doblan la carga de la programacin. ASP pierde
el control con los valores NULLS y es necesario invertir mucho esfuerzo para lograr
algo. A menos que los necesite realmente, de verdad preocpese de los valores perdidos,
no querremos usar NULLs absolutamente.
En segundo lugar, el SQL tiene palabras reservadas que tienen que ser directamente
encerradas entre parntesis si las usa como nombres de campo. Yo no las uso porque
tengo mis mtodos. Pero a menudo tenemos que sustentar la herencia de nuestras
aplicaciones y eso significa usar dos sistemas en paralelo por un perodo de tiempo. Por
lo que si necesitramos usar una palabra reservada como nombre de columna
deberamos encerrarla entre parntesis mientras contruyamos la definicin de la tabla.
El programa de conversin de datos le proporciona una lista de palabras claves de SQL
que haya usado como los nombres de los campos. Muestro la lista con los sospechosos
habituales.
Finalmente, para hacer nuestra actualizacin ms fcil, es una buena idea mantener un
nmero entero como ndice primario para cada tabla, sobre todo si tenemos otros
campos de texto que hemos estado usando como llaves secundarias. La razn es que esa
llave nica es esencial si queremos hacer la codificacin de actualizacin lo ms simple
posible, puesto que cada tabla debe de tener uno. En el mundo de FoxPro hemos
desarrollado el mal hbito de usar un ndice compuesto (por ejemplo PONum+LineNum
para guardar los artculos de las ordenes de compra) que simplemente es una pesadilla
para codificar en alguna moda genrica.
El Servidor del SQL ofrece una facilidad de autoincremento llamada IDENTITY. Por
ejemplo, puede declarar un campo entero nombrado MyKey y luego IDENTITY(1,1)
(empieza con 1 e incrementa por 1) en, y cada vez que INSERTE un registro un nuevo
valor aparecer. El problema es que si necesita el acceso instantneo a ese valor,
necesita agregar el comando SELECTO @@IDENTITY despus de la INSERCIN
(por ejemplo, despus de agregar un ttulo de Factura y antes de insertar las Lneas de
Detalle relacionadas con la Factura) para proporcionar el ndice del ttulo en el detalle
para unirlas subsecuente. Y hay otras razones. Si tiene un campo IDENTITY, no debe
incluir su nombre en cualquier orden de INSERT. As que su cdigo de actualizacin
tiene que saber saltar el campo si el mismo es un campo IDENTITY.
or
Ya estamos listos para construir la base de datos de SQL y cargar los datos. La manera
fcil es abrir el Enterprise Manager y crear los archivos MDF y LDF manualmente,
estimando el tamao inicial de cada uno. O podemos usar simplemente el comando
CREATE DATABASE y dejar que el SQL les asigne un tamao predefinido de medio
megabyte a cada fichero. Podemos especificar dnde poner estos archivos, pero es
preferible mantener su valor predeterminado, que podra ser: Program Files\Microsoft
SQL Server\MSSQL\Data.
Le aconsejo adems crear un userid y su contrasea para que entre a la base de datos.
Nuevamente, podemos hacerlo con el Enterprise Manager o en el cdigo, pero visual es
mejor. Vamos al tabulador Security para agregar un inicio de sesin, asignando (por
ahora) todos los derechos al userid. Permita al DBAs preocuparse por la afinacin de la
seguridad. Ellos quieren dejar fuera a las personas; nosotros queremos dejarlos entrar.
Para los propsitos de este artculo, asumir que ha realizado estos dos pasos, y que el
siguiente comando pueda manejar su conexin:
Handle debe ser un nmero entero positivo, ejemplo 1, 2, 3, etc. Para cerrar la conexin
debe usar SQLDISCONNECT(0). Ya estamos listos. El siguiente programa le
preguntar donde estn sus DBFs y las carga a la base de datos nombrada anteriormente
en el servidor de SQL:
CLEAR
CLOSE ALL
SET STRICTDATE TO 0
SET EXCLUSIVE ON
SET CONFIRM ON
ConnStr = [Driver={SQL
Server};Server=(local);UID=sa;PWD=;Database=NorthWind;]
IF Handle < 1
MESSAGEBOX( "Imposible conectar con SQL" + CHR(13) + ConnStr, 16 )
RETURN
ENDIF
ReservedWords =
[,DESC,DATE,RESERVED,PRINT,ID,VIEW,BY,DEFAULT,CURRENT,KEY,ORDER,CHECK,
FROM,TO,]
set step on
RETURN
ENDIF
ENDIF
ASORT(laDBFS,1)
FOR I = 1 TO ALEN(laDBFS,1)
USE ( laDBFS(I,1))
LoadOneTable()
ENDFOR
SQLDISCONNECT(0)
_VFP.Caption = [Done]
PROCEDURE LoadOneTable
LOCAL I
cRecCount = TRANSFORM(RECCOUNT())
cmd = [DROP TABLE ] + ALIAS()
&& agregue
las suyas aqu.
? [Skipping ] + ALIAS()
RETURN
ENDIF
SCAN
FOR I = 1 TO FCOUNT()
fld = FIELD(I)
IF TYPE(Fld) = [G]
LOOP
ENDIF
dta = &Fld
typ = VARTYPE(dta)
cdta = ALLTRIM(TRANSFORM(dta))
DO CASE
cDta = []
ENDIF
IF cDta = [/ /]
cDta = []
ENDIF
ENDCASE
ENDFOR
Cmd = LEFT(Cmd,LEN(cmd)-2) + [ )]
IF lr < 0
? [Error: ] + Cmd
SUSPEND
ENDIF
ENDSCAN
WAIT CLEAR
PROCEDURE CreateTable
LOCAL J
AFIELDS(laFlds)
FOR J = 1 TO ALEN(laFlds,1)
IF laFlds(J,2) = [G]
LOOP
ENDIF
FldName = laFlds(J,1)
ENDIF
DO CASE
Ahora, estamos listos para escribir nuestra aplicacin de ejemplo. Quiero poder cambiar
a voluntad entre DBFs y SQL, para poder verificar que mi programa trabaja de la misma
manera con ambos bancos de datos. Por lo tanto mi men (he agregado llamadas a un
par de pantallas que construiremos en este artculo) quedar de la siguiente forma:
Listing 2....Menu.MPR
SET SYSMENU TO
PROCEDURE ChangeAccess
Mi MAIN.PRG fue creado con unas pocas funciones, poner un ttulo en la pantalla,
instanciar mi objeto DataTier, posicionar el men e iniciar el ciclo de eventos:
Listing 3....: MAIN.PRG
* Purpose.....: Programa PRINCIPAL para la aplicacin
CLEAR ALL
CLOSE ALL
CLEAR
CLOSE ALL
SET TALK OFF
SET CONFIRM ON
SET MULTILOCKS ON
SET CENTURY ON
SET EXCLUSIVE ON
SET SAFETY OFF
SET DELETED ON
SET STRICTDATE TO 0
WITH _Screen
.AddObject ( [Title1], [Title], 0, 0 )
.AddObject ( [Title2], [Title], 3, 3 )
.Title2.ForeColor = RGB ( 255, 0, 0 )
ENDWITH
DO MENU.MPR
ON ERROR
SET PROCEDURE TO
SET CLASSLIB TO
WITH _Screen
.RemoveObject ( [Title1] )
.RemoveObject ( [Title2] )
ENDWITH
PROCEDURE Init
LPARAMETERS nTop, nLeft
THIS.Top = _Screen.Height - 100 - nTop
THIS.Left= 25 - nLeft
ENDPROC
ENDDEFINE
PROCEDURE ErrTrap
LPARAMETERS nLine, cProg, cMessage, cMessage1
OnError = ON("Error")
ON ERROR
IF NOT FILE ( [ERRORS.DBF] )
CREATE TABLE ERRORS ( ;
Date Date, ;
Time Char(5), ;
LineNum Integer, ;
ProgName Char(30), ;
Msg Char(240), ;
CodeLine Char(240) )
ENDIF
IF NOT USED ( [Errors] )
USE ERRORS IN 0
ENDIF
SELECT Errors
INSERT INTO Errors VALUES ( DATE(), LEFT(TIME(),5), nLine, cProg,
cMessage, cMessage1 )
USE IN Errors
Al final del artculo, podr hacer clic en l y probar los formularios que usan distintas
fuentes de datos. El cdigo fuente esta listo para descargar en la ltima pgina. Podrn
descargar este o cualquier otro cdigo fuente desde mi sitio. Vea
http://www.lespinter.com/ para ms detalles.
Lo que resulta un poco complicado es entender como los formularios plantilla y la capa
de datos interactan recprocamente. Durante la depresin, mi padre tena una manera
humorstica para ablandar la situacin difcil, era decir a alguien "si tuviramos un poco
de jamn, podramos tener jamn y huevos, si tuviramos algunos huevos... ". La
plantilla del formulario y la capa de datos se escriben cada una con la otra en la mente.
Los componentes reusables entran la capa de datos, mientras las partes procesales "paso
a paso" entran la plantilla.
KeyValue = .F. && Valor del ndice del campo del record actual
LPARAMETERS OnOff
WITH THISFORM
.cmdAdd.Enabled = OnOff
.cmdFind.Enabled = OnOff
.cmdClose.Enabled = OnOff
.cmdClose.Cancel = OnOff
ENDWITH
ENDPROC
LPARAMETERS OnOff
WITH THISFORM
Ctrl.Enabled = OnOff
ENDIF
ENDFOR
ENDWITH
ENDPROC
WITH THISFORM
IF EMPTY ( .MainTable )
RETURN .F.
ENDIF
ENDWITH
ENDPROC
PROCEDURE Init && Se ejecuta despus que los botones han sido
inicializados
THISFORM.Buttons ( .T. )
ENDPROC
1. Asigne los mapeos de los campos para use la librera Pinter.VCX y los controles
apropiados (MyText, MyChk, etc) para los tipos de datos que usar en sus
tablas;
2. Ejecute CREATE FORM "NombreFormulario" AS FlatFileForm FROM Pinter;
3. Abra el formulario, agregue el DBF que ser la tabla principal del formulario al
Data Environment, arrastre los campos hacia el formulario, y elimnela
seguidamente del Data Environment.
4. Cambie el orden de tabulacin y asigne a cmdAdd y cmdEdit las primeras
posiciones en la lista de tabulacin;
5. Asigne a las propiedades MainTable y KeyField del formulario los valores
correspondientes;
6. Elimine cualquier campo que no desee que los usuarios puedan entrar o editar,
como CreateDate o RecordID.
(Si lo prefiere puede ejecutar un BROWSE sobre su .SCX, buscar el control que
desea hacer no editable, y cambiar el valor en el campo Class del .SCX a noedit,
pues el control TextBox se encuentra deshabilitado en Pinter.Vcx. Ya que no
aparece en la propiedad InputFields como campo editable nunca ser editable.
Simplemente rellene tres o cuatro propiedades, nombre los campos de entrada que puso
en el formulario de bsqueda con los nombres SEARCH1, SEARCH2, SEARCH3 y
SEARCH4, y asigne el orden de tabulacin en el formulario, y todo est hecho.
Aqu el cdigo:
PROCEDURE Init
WITH THISFORM
.Caption = [Formulario de Bsqueda - ] + .Name + [ (Tabla Principal: ]
;
+ TRIM(.TableName)+[) Data access: ] + .Access
NumWords = GETWORDCOUNT(.ColNames,[,])
IF NumWords > 4
MESSAGEBOX( [Esta clase solo soporta 4 campos como mximo, lo
siento], ;
16, _VFP.Caption )
RETURN .F.
ENDIF
FOR I = 1 TO NumWords
.Field(I) = GETWORDNUM(.ColNames, I,[,])
.Heading(I) = GETWORDNUM(.ColHeadings,I,[,])
.ColWidth(I)= GETWORDNUM(.ColWidths, I,[,])
ENDFOR
WITH .Grid1
.ColumnCount = NumWords
.RecordSource = THISFORM.ViewName
.RecordSourceType = 1
GridWidth = 0
FOR I = 1 TO NumWords
.Columns(I).Header1.Caption = THISFORM.Heading (I)
GridWidth = GridWidth + VAL( THISFORM.ColWidth(I) )
FldName = THISFORM.ViewName + [.] + THISFORM.Field (I)
.Columns(I).ControlSource = FldName
ENDFOR
Multiplier = ( THIS.Width / GridWidth ) * .90 && "Fudge" factor
FOR I = 1 TO NumWords
.Columns(I).Width = VAL( THISFORM.ColWidth(I) ) * Multiplier
ENDFOR
.Refresh
ENDWITH
* Buscar cualquier control llamado SEARCHn (n = 1, 2, ... )
FOR I = 1 TO .ControlCount
Ctrl = .Controls(I)
IF UPPER(Ctrl.Name) = [MYLABEL] && Esto es, si
comienza con "MyLabel"
Sub = RIGHT(Ctrl.Name,1) && Determinar el index
IF TYPE([THISFORM.Search]+Sub)=[O] && Un campo de bsqueda
#"Sub" existe
Ctrl.Visible = .T.
Ctrl.Enabled = .T.
Ctrl.Caption = .Heading(VAL(Sub))
.SearchFieldCount = MAX ( VAL(Sub), .SearchFieldCount )
ENDIF
ENDIF
ENDFOR
.SetAll ( "Enabled", .T. )
ENDWITH
ENDPROC
PROCEDURE Load
WITH THISFORM
IF EMPTY ( .TableName )
RETURN .F.
ENDIF
IF EMPTY ( .ColNames )
RETURN .F.
ENDIF
IF EMPTY ( .ColWidths )
.ColWidths = [1,1,1,1,1]
ENDIF
IF EMPTY ( .ColHeadings )
.ColHeadings = .ColNames
ENDIF
.Access = oDataTier.AccessMethod
.ViewName = [View] + .TableName
oDataTier.CreateView ( .TableName )
ENDWITH
ENDPROC
PROCEDURE Unload
WITH THISFORM
IF USED ( .ViewName )
USE IN ( .ViewName )
ENDIF
RETURN .ReturnValue
ENDWITH
ENDPROC
PROCEDURE cmdShowMatches.Click
WITH THISFORM
STORE [] TO Expr1,Expr2,Expr3,Expr4
FOR I = 1 TO .SearchFieldCount
ENDIF
ENDFOR
ENDIF
+ ALLTRIM(STRTRAN(.OrderBy,[ORDER BY],[])))
PROCEDURE cmdClear.Click
WITH THISFORM
FOR I = 1 TO .SearchFieldCount
Fld = [THISFORM.Search] + TRANSFORM(I) + [.Value]
IF VARTYPE ( &Fld ) <> [U]
lVal = IIF ( VARTYPE( &Fld) = [C], [], ;
IIF ( VARTYPE( &Fld) = [D], {//}, ;
IIF ( VARTYPE( &Fld) = [L], .F., ;
IIF ( VARTYPE( &Fld) $ [IN], 0, [?]))))
&Fld = lVal
ENDIF
ENDFOR
ENDWITH
ENDPROC
PROCEDURE cmdSelect.Click
WITH THISFORM
lcStrValue = TRANSFORM(EVALUATE(.KeyField))
.ReturnValue = lcStrValue
.Release
ENDWITH
ENDPROC
PROCEDURE cmdCancel.Click
WITH THISFORM
.ReturnValue = []
.Release
ENDWITH
ENDPROC
ENDDEFINE
La capa de datos
Sin embargo, si est usando SQL o un XML Web Service, est solo a mitad de camino.
Hasta el momento, solo hemos hecho el equivalente a salvar los cambios a un Dataset
en .NET. Ahora necesita usar los datos guardados para actualizarlos, los cuales podran
estar en el Servidor del SQL o en Timbuktu. Por lo que an necesitamos TableUpdate(),
TableRevert() y las llamadas a los botones Save y Cancel. Apenas tenemos que agregar
una llamada ms al objeto DataTier y ver qu falta. Si usa DBFs, como usted ver,
simplemente disclpese y retorne. Similarmente, en el evento Load del formulario,
llamamos al mtodo CreateCursor del objeto, en ambos casos crea un cursor, o, en el
caso de acceso a un DBF, abre la tabla apropiada y su archivo ndice.
La manera ptima de leer este cdigo es encontrar el lugar en la plantilla del formulario
donde es llamada cada rutina de cdigo, entonces podremos ver los procedimientos que
DataTier ejecuta seguidamente despus que son invocados en la plantilla. Es
precisamente esta combinacin del cdigo que radica en la plantilla y la capa de datos
donde aparece la magia. He aqu el cdigo de la capa de datos: Cualquier esfuerzo por
asignar un valor a esta propiedad se entrampar por el mtodo del "setter"
AccessMethod_Assign.
ConnectionString = [Driver={SQL
Server};Server=(local);Database=Northwind;UID=sa;PWD=;]
Handle = 0
Apuesto que no conoca que poda escribir sus propios mtodos Assign ...
PROCEDURE AccessMethod_Assign
PARAMETERS AM
DO CASE
CASE AM = [DBF]
CASE AM = [SQL]
THIS.GetHandle
CASE AM = [XML]
CASE AM = [WC]
OTHERWISE
THIS.AccessMethod = []
ENDCASE
_VFP.Caption = [Data access method: ] + THIS.AccessMethod
ENDPROC
CreateCursor abre el DBF si AccessMethod realmente es DBF; por otra parte usa una
estructura devuelta de la fuente de datos para crear un cursor que se vincula a los
controles de la pantalla:
PROCEDURE CreateCursor
LPARAMETERS pTable, pKeyField
IF THIS.AccessMethod = [DBF]
IF NOT USED ( pTable )
SELECT 0
USE ( pTable ) ALIAS ( pTable )
ENDIF
SELECT ( pTable )
IF NOT EMPTY ( pKeyField )
SET ORDER TO TAG ( pKeyField )
ENDIF
RETURN
ENDIF
Cmd = [SELECT * FROM ] + pTable + [ WHERE 1=2]
DO CASE
CASE THIS.AccessMethod = [SQL]
SQLEXEC( THIS.Handle, Cmd )
AFIELDS ( laFlds )
USE
CREATE CURSOR ( pTable ) FROM ARRAY laFlds
CASE THIS.AccessMethod = [XML]
CASE THIS.AccessMethod = [WC]
ENDCASE
PROCEDURE GetHandle
IF THIS.AccessMethod = [SQL]
IF THIS.Handle > 0
RETURN
ENDIF
THIS.Handle = SQLSTRINGCONNECT( THIS.ConnectionString )
IF THIS.Handle < 1
MESSAGEBOX( [Unable to connect], 16, [SQL Connection error],
2000 )
ENDIF
ELSE
Msg = [A SQL connection was requested, but access method is ] +
THIS.AccessMethod
MESSAGEBOX( Msg, 16, [SQL Connection error], 2000 )
THIS.AccessMethod = []
ENDIF
RETURN
PROCEDURE GetMatchingRecords
LPARAMETERS pTable, pFields, pExpr
pFields = IIF ( EMPTY ( pFields ), [*], pFields )
pExpr = IIF ( EMPTY ( pExpr ), [], ;
[ WHERE ] + STRTRAN ( UPPER ( ALLTRIM ( pExpr ) ), [WHERE ],
[] ) )
cExpr = [SELECT ] + pFields + [ FROM ] + pTable + pExpr
IF NOT USED ( pTable )
RetVal = THIS.CreateCursor ( pTable )
ENDIF
DO CASE
CASE THIS.AccessMethod = [DBF]
&cExpr
CASE THIS.AccessMethod = [SQL]
THIS.GetHandle()
IF THIS.Handle < 1
RETURN
ENDIF
lr = SQLExec ( THIS.Handle, cExpr )
IF lr >= 0
THIS.FillCursor()
ELSE
Msg = [Unable to return records] + CHR(13) + cExpr
MESSAGEBOX( Msg, 16, [SQL error] )
ENDIF
ENDCASE
ENDPROC
PROCEDURE CreateView
LPARAMETERS pTable
IF NOT USED( pTable )
MESSAGEBOX( [Can't find cursor ] + pTable, 16, [Error creating
view], 2000 )
RETURN
ENDIF
SELECT ( pTable )
AFIELDS( laFlds )
SELECT 0
CREATE CURSOR ( [View] + pTable ) FROM ARRAY laFlds
ENDFUNC
PROCEDURE GetOneRecord
LPARAMETERS pTable, pKeyField, pKeyValue
SELECT ( pTable )
Dlm = IIF ( TYPE ( pKeyField ) = [C], ['], [] )
IF THIS.AccessMethod = [DBF]
cExpr = [LOCATE FOR ] + pKeyField + [=] + Dlm + TRANSFORM
( pKeyValue ) + Dlm
ELSE
cExpr = [SELECT * FROM ] + pTable + [ WHERE ] + pKeyField + [=] + ;
Dlm + TRANSFORM ( pKeyValue ) + Dlm
ENDIF
DO CASE
CASE THIS.AccessMethod = [DBF]
&cExpr
CASE THIS.AccessMethod = [SQL]
lr = SQLExec ( THIS.Handle, cExpr )
IF lr >= 0
THIS.FillCursor( pTable )
ELSE
Msg = [Unable to return record] + CHR(13) + cExpr
MESSAGEBOX( Msg, 16, [SQL error] )
ENDIF
CASE THIS.AccessMethod = [XML]
CASE THIS.AccessMethod = [WC]
ENDCASE
ENDFUNC
PROCEDURE FillCursor
LPARAMETERS pTable
IF THIS.AccessMethod = [DBF]
RETURN
ENDIF
SELECT ( pTable )
ZAP
APPEND FROM DBF ( [SQLResult] )
USE IN SQLResult
GO TOP
ENDPROC
Quisiera pensar que todos los ndices primarios sern enteros, pero los sistemas del
antiguos a veces tienen ndices de carcter. Esta es la razn por la que chequeo los
separadores en la rutina DeleteRecord:
PROCEDURE DeleteRecord
LPARAMETERS pTable, pKeyField
IF THIS.AccessMethod = [DBF]
RETURN
ENDIF
KeyValue = EVALUATE ( pTable + [.] + pKeyField )
Dlm = IIF ( TYPE ( pKeyField ) = [C], ['], [] )
DO CASE
CASE THIS.AccessMethod = [SQL]
cExpr = [DELETE ] + pTable + [ WHERE ] + pKeyField + [=] + ;
Dlm + TRANSFORM ( m.KeyValue ) + Dlm
lr = SQLExec ( THIS.Handle, cExpr )
IF lr < 0
Msg = [Unable to delete record] + CHR(13) + cExpr
MESSAGEBOX( Msg, 16, [SQL error] )
ENDIF
CASE THIS.AccessMethod = [XML]
CASE THIS.AccessMethod = [WC]
ENDCASE
ENDFUNC
PROCEDURE SaveRecord
PARAMETERS pTable, pKeyField, pAdding
IF THIS.AccessMethod = [DBF]
RETURN
ENDIF
IF pAdding
THIS.InsertRecord ( pTable, pKeyField )
ELSE
THIS.UpdateRecord ( pTable, pKeyField )
ENDIF
ENDPROC
PROCEDURE InsertRecord
LPARAMETERS pTable, pKeyField
cExpr = THIS.BuildInsertCommand ( pTable, pKeyField )
_ClipText = cExpr
DO CASE
CASE THIS.AccessMethod = [SQL]
lr = SQLExec ( THIS.Handle, cExpr )
IF lr < 0
msg = [Unable to insert record; command follows:] + CHR(13)
+ cExpr
MESSAGEBOX( Msg, 16, [SQL error] )
ENDIF
CASE THIS.AccessMethod = [XML]
CASE THIS.AccessMethod = [WC]
ENDCASE
ENDFUNC
PROCEDURE UpdateRecord
LPARAMETERS pTable, pKeyField
cExpr = THIS.BuildUpdateCommand ( pTable, pKeyField )
_ClipText = cExpr
DO CASE
CASE THIS.AccessMethod = [SQL]
lr = SQLExec ( THIS.Handle, cExpr )
IF lr < 0
msg = [Unable to update record; command follows:] + CHR(13)
+ cExpr
MESSAGEBOX( Msg, 16, [SQL error] )
ENDIF
CASE THIS.AccessMethod = [XML]
CASE THIS.AccessMethod = [WC]
ENDCASE
ENDFUNC
FUNCTION BuildInsertCommand
PARAMETERS pTable, pKeyField
Cmd = [INSERT ] + pTable + [ ( ]
FOR I = 1 TO FCOUNT()
Fld = UPPER(FIELD(I))
IF TYPE ( Fld ) = [G]
LOOP
ENDIF
Cmd = Cmd + Fld + [, ]
ENDFOR
Cmd = LEFT(Cmd,LEN(Cmd)-2) + [ } VALUES ( ]
FOR I = 1 TO FCOUNT()
Fld = FIELD(I)
IF TYPE ( Fld ) = [G]
LOOP
ENDIF
Dta = ALLTRIM(TRANSFORM ( &Fld ))
Dta = CHRTRAN ( Dta, CHR(39), CHR(146) ) && librse de comillas
en los datos
Dta = IIF ( Dta = [/ /], [], Dta )
Dta = IIF ( Dta = [.F.], [0], Dta )
Dta = IIF ( Dta = [.T.], [1], Dta )
Dlm = IIF ( TYPE ( Fld ) $ [CM],['],;
IIF ( TYPE ( Fld ) $ [DT],['],;
IIF ( TYPE ( Fld ) $ [IN],[], [])))
Cmd = Cmd + Dlm + Dta + Dlm + [, ]
ENDFOR
Cmd = LEFT ( Cmd, LEN(Cmd) -2) + [ )] && Remove ", " add " )"
RETURN Cmd
ENDFUNC
FUNCTION BuildUpdateCommand
PARAMETERS pTable, pKeyField
Cmd = [UPDATE ] + pTable + [ SET ]
FOR I = 1 TO FCOUNT()
Fld = UPPER(FIELD(I))
IF Fld = UPPER(pKeyField)
LOOP
ENDIF
IF TYPE ( Fld ) = [G]
LOOP
ENDIF
Dta = ALLTRIM(TRANSFORM ( &Fld ))
IF Dta = [.NULL.]
DO CASE
CASE TYPE ( Fld ) $ [CMDT]
Dta = []
CASE TYPE ( Fld ) $ [INL]
Dta = [0]
ENDCASE
ENDIF
Dta = CHRTRAN ( Dta, CHR(39), CHR(146) ) && librse de
comillas en los datos
Dta = IIF ( Dta = [/ /], [], Dta )
Dta = IIF ( Dta = [.F.], [0], Dta )
Dta = IIF ( Dta = [.T.], [1], Dta )
Dlm = IIF ( TYPE ( Fld ) $ [CM],['],;
IIF ( TYPE ( Fld ) $ [DT],['],;
IIF ( TYPE ( Fld ) $ [IN],[], [])))
Cmd = Cmd + Fld + [=] + Dlm + Dta + Dlm + [, ]
ENDFOR
Dlm = IIF ( TYPE ( pKeyField ) = [C], ['], [] )
Cmd = LEFT ( Cmd, LEN(Cmd) -2 ) ;
+ [ WHERE ] + pKeyField + [=] ;
+ + Dlm + TRANSFORM(EVALUATE(pKeyField)) + Dlm
RETURN Cmd
ENDFUNC
En ocasiones, necesito devolver un cursor que usar para otros propsitos, por ejemplo,
llenar un combobox. Utilizo el nombre predeterminado SQLResult. Menciono este
detalle porque si utilizo el SELECT de Foxpro devuelve el cursor por defecto en un
BROWSE, y necesito la certeza que el nombre del cursor ser SQLResult cuando
retorne de este procedimiento:
PROCEDURE SelectCmdToSQLResult
LPARAMETERS pExpr
DO CASE
CASE THIS.AccessMethod = [DBF]
pExpr = pExpr + [ INTO CURSOR SQLResult]
&pExpr
CASE THIS.AccessMethod = [SQL]
THIS.GetHandle()
IF THIS.Handle < 1
RETURN
ENDIF
lr = SQLExec ( THIS.Handle, pExpr )
IF lr < 0
Msg = [Unable to return records] + CHR(13) + cExpr
MESSAGEBOX( Msg, 16, [SQL error] )
ENDIF
CASE THIS.AccessMethod = [XML]
CASE THIS.AccessMethod = [WC]
ENDCASE
ENDFUNC
Cuando agrego un nuevo registro, sea DBFS o SQL, soy total responsable al insertar un
nico valor en la tabla. Mantengo una tabla con nombres de las tablas y el ltimo ndice
usado. Si crea esta tabla manualmente, asegrese de tener actualizado el LastKeyVal
manualmente, o conseguir miles de llaves duplicadas.<G>
FUNCTION GetNextKeyValue
LPARAMETERS pTable
EXTERNAL ARRAY laVal
pTable = UPPER ( pTable )
DO CASE
CASE THIS.AccessMethod = [DBF]
IF NOT FILE ( [Keys.DBF] )
CREATE TABLE Keys ( TableName Char(20), LastKeyVal
Integer )
ENDIF
IF NOT USED ( [Keys] )
USE Keys IN 0
ENDIF
SELECT Keys
LOCATE FOR TableName = pTable
IF NOT FOUND()
INSERT INTO Keys VALUES ( pTable, 0 )
ENDIF
Cmd = [UPDATE Keys SET LastKeyVal=LastKeyVal + 1 ] ;
+ [ WHERE TableName='] + pTable + [']
&Cmd
Cmd = [SELECT LastKeyVal FROM Keys WHERE TableName = '] ;
+ pTable + [' INTO ARRAY laVal]
&Cmd
USE IN Keys
RETURN TRANSFORM(laVal(1))
ENDCASE
ENDDEFINE
Ejecutando la aplicacin
Teclee BUILD EXE ThreeTier FROM ThreeTier, o pulse el botn Build en el Project
Manager. Concludo el proceso, ejectelo y pruebe cada formulario, y las pantallas de
bsqueda. Funciona bastante bien, tal como hasta ahora hemos visto con los DBFs.
Probemos entonces de una forma diferente, seleccione Change Data Source desde el
men y seleccione SQL, como se muestra en la Fig. 4.
Figura 4: Cambiando el mtodo de acceso a los datos
En este instante, abra el formulario Customers. No existen datos! Eso es normal para
una aplicacin de SQL, porque no se han realizado peticiones de registros por el
usuario, todava no se han hecho. Haga clic en Find, teclee algn criterio de bsqueda
(realmente, para devolcer todos los registros djelos en blanco) haga clic sobre Show
Matches, y seleccione uno. El formulario de bsqueda desaparecer, y tendr sus
datos!. Seleccione Edit, cambie algo y slvelo. Cargue nuevamente la pgina para
verificar que todo funciona. Agregue un artculo, slvelo, y compruebe que aparece en
el formulario de bsqueda.
Proximamente adicionaremos soporte para XML Web Services y para el tan venerable
WebConnection.
Nos vemos.
Cdigo Fuente
Les Pinter es miembro del INETA Speaker's Bureau y director de Pinter Consulting en
San Mateo, California. Publica boletines bimestrales para desarrolladores con
artculos sobre Visual Foxpro, VB.NET, ASP.NET y SQL. Con frecuencia es orador en
grupos de usuarios y conferencias en Estados Unidos y el extranjero, dialogando en
Ingls, Portugus, Espaol, Ruso y Francs. Les tambin es piloto privado.