MANEJADORES DE BASE DE DATOS IBM Informix® Dynamic Server (IDS) 9.30 proporciona fiabilidad superior, atendiendo las necesidades de las exigentes prácticas actuales del e−business−particularmente para aplicativos que requieran transacciones de alto desempeño. Soporta requisitos de procesamiento de transacción online, complejos y rigurosos. Optimiza capacidades de inteligencia del negocio competitivas Maximiza operaciones de datos para el grupo de trabajo y para la empresa en total. Proporciona la firmeza de una administración de base de datos comprobada, mejor de su especie. Informix Dynamic Server con J/Foundation combina las características de IDS con un ambiente abierto, flexible, empotrado de Java! Virtual Machine. IDS con J/Foundation permite que los desarrolladores de base de datos escriban lógica de negocio del lado−servidor usando el lenguaje Java!. Java User Defined Routines (UDRs) tienen completo acceso a las características de la base de datos extensible líder mundial, de la base de datos IDS. Haciendo del IDS la plataforma ideal para el desarrollo de base de datos Java. Además de Java UDRs, el IDS está en conformidad con el estándar SQLJ para procedimientos almacenados en Java, permitiendo el uso de los paquetes Java estándar que se encuentran incluidos en el Java Development Kit (JDK). Escribir UDRs en Java proporciona aplicativos mucho más flexibles que se pueden desarrollar más rápidamente que C, y más potentes y administrables que los lenguajes de procedimientos almacenados. Una extensión adicional de escribir UDRs en Java es escribir módulos DataBlade® en Java. Los módulos DataBlade son colecciones de nuevas funciones del lado−servidor y tipos de datos puestos en conjunto para extender el IBM Informix® Dynamic Server con el servidor de datos J/Foundation. El DataBlade Developer's Kit (DBDK) ahora soporta Java y permite el desarrollo, diseminación y depuración de UDRs en Java. La tecnología IBM Informix DataBlade es líder en la industria en extender el servidor para permitir tanto la administración de contenido rich, cuanto la lógica de negocio. J/Foundation está provisto con IDS en muchas de las plataformas IDS 9.30 soportadas. Las plataformas soportadas incluyen Sun Solaris 32 bit, Microsoft Windows NT/2000, Linux, IBM AIX, SGI Irix, y Compaq Tru 64 IBM Informix® Dynamic Server (IDS) 9.30 proporciona fiabilidad superior, atendiendo las necesidades de las exigentes prácticas actuales del e−business−particularmente para aplicativos que requieran transacciones de alto desempeño. Soporta requisitos de procesamiento de transacción online, complejos y rigurosos. Optimiza capacidades de inteligencia del negocio competitivas Maximiza operaciones de datos para el grupo de trabajo y para la empresa en total. Proporciona la firmeza de una administración de base de datos comprobada, mejor de su especie. Informix Dynamic Server con J/Foundation combina las características de IDS con un ambiente abierto, flexible, empotrado de Java! Virtual Machine. IDS con J/Foundation permite que los desarrolladores de base 1 de datos escriban lógica de negocio del lado−servidor usando el lenguaje Java!. Java User Defined Routines (UDRs) tienen completo acceso a las características de la base de datos extensible líder mundial, de la base de datos IDS. Haciendo del IDS la plataforma ideal para el desarrollo de base de datos Java. Además de Java UDRs, el IDS está en conformidad con el estándar SQLJ para procedimientos almacenados en Java, permitiendo el uso de los paquetes Java estándar que se encuentran incluidos en el Java Development Kit (JDK). Escribir UDRs en Java proporciona aplicativos mucho más flexibles que se pueden desarrollar más rápidamente que C, y más potentes y administrables que los lenguajes de procedimientos almacenados. Una extensión adicional de escribir UDRs en Java es escribir módulos DataBlade® en Java. Los módulos DataBlade son colecciones de nuevas funciones del lado−servidor y tipos de datos puestos en conjunto para extender el IBM Informix® Dynamic Server con el servidor de datos J/Foundation. El DataBlade Developer's Kit (DBDK) ahora soporta Java y permite el desarrollo, diseminación y depuración de UDRs en Java. La tecnología IBM Informix DataBlade es líder en la industria en extender el servidor para permitir tanto la administración de contenido rich, cuanto la lógica de negocio. J/Foundation está provisto con IDS en muchas de las plataformas IDS 9.30 soportadas. Las plataformas soportadas incluyen Sun Solaris 32 bit, Microsoft Windows NT/2000, Linux, IBM AIX, SGI Irix, y Compaq Tru 64 Nombre del Formato: Tipo de Formato: Descripción del Formato: Formato disponible en Data Junction Enterprise: Formato disponible en Data Junction Profesional: Nombre del Editor: Más información en: Comentarios adicionales: Informix Base de Datos Base de datos comercial Sí No (la versión Enterprise es necesaria si se desea leer o escribir datos en este formato) IBM Software Informix Sin comentarios Funciones Oracle Requisitos previos: Usted necesita saber los valores de las variables de entorno siguientes: ORACLE_HOME Ésta es la trayectoria a su directorio de la instalación del oráculo. Se define generalmente en la escritura de la conexión de UNIX de su usuario del oráculo y de todos los usuarios del cliente del oráculo. ORACLE_SID Éste es el nombre del caso de la base de datos que usted desea conectar con. También se define en el ambiente de UNIX de su usuario del oráculo y de todos los usuarios del cliente del oráculo. Descubra los valores de estas variables por el loggin adentro como usuario que pueda conectar con la base de datos en la pregunta con sqlplus. Entonces mecanografíe en su aviso de la cáscara de Unix: prompt> echo $ORACLE_HOME /opt/oracle/oracle/8.0.3 prompt> echo $ORACLE_SID ORACLE 2 A simple PHP script using ora_* functions <?php putenv("ORACLE_SID=ORACLE"); putenv("ORACLE_HOME=/opt/oracle/oracle/8.0.3"); $conn = ora_login("scott", "tiger"); $curs = ora_open($conn); ora_commitoff($conn); $query = sprintf(&quotselect * from cat"); /* Long version */ /* ora_parse($curs, $query); ora_exec($curs); ora_fetch($curs); */ /* Short Version */ ora_do($conn, $query); $ncols = ora_numcols($curs); $nrows = ora_numrows($curs); printf("Result size is $ncols cols by $nrows rows. "); for ($i=0; $i<$ncols; $i++) { printf("col[%s] = %s type[%d] = %s ", $i, ora_columnname($curs, $i), $i, ora_columntype($curs, $i)); } for ($j=0; $j<$nrows; $j++) { for ($i=0; $i<$ncols; $i++) { $col = ora_getcolumn($curs, $i); printf(&quotval[%d, %d] = %s * ", $j, $i, ora_getcolumn($curs, $i); } printf(" "); } ?> Stevel at nettek−llc dot com <?php putenv( 3 "ORACLE_SID=sid1"); putenv( "ORACLE_HOME=/u01/app/oracle/product/8.0.5"); $handle = ora_plogon( "SCOTT@sid1", "TIGER")or die; $cursor = ora_open($handle); ora_commitoff($handle); $query = "SELECT * FROM EMP"; ora_parse($cursor, $query) or die; ora_exec($cursor); echo "<HTML><PRE>\n"; echo "$query\n\n"; $numcols = 0; while(ora_fetch($cursor)){ $numcols = ora_numcols($cursor); for($column=0; $column < $numcols; $column++){ $data = trim(ora_getcolumn($cursor, $column)); if($data =="") $data = "NULL"; echo"$data\t"; } echo "\n"; } $numrows = ora_numrows($cursor); echo "\nROWS RETURNED: $numrows\n"; 4 echo "</PRE></HTML>\n"; ora_close($cursor); ?> Vistas y envolturas La implementación de VFP de las vistas le permite trabajar con ellas de modo similar a como trabaja con tablas nativas. Como las trataré en detalle en este artículo, usarlas es clave para lograr ser independiente del software de soporte (back−end). El otro concepto importante es el uso de envolturas. Por envoltura me refiero a la técnica por la cual una función o comando, como SKIP, es "envuelta" en algún código que la protege y almacenada en una función definida por el usuario, de forma tal que cada vez que se utilice, no tenga que codificar de nuevo la intercepción de errores necesaria. Por ejemplo, la función SafeSkip puede verse como: Function SafeSkip LParameters cAlias llMoved = .F. * Avoid that "EOF encountered" error If !EOF(cAlias) Skip In (cAlias) If EOF(cAlias) * Moved to EOF, retreat Skip −1 In (cAlias) Else llMoved = .T. Endif Endif Return llMoved Mientras más fuentes de datos intente accesar, más código condicional necesitará escribir. Escribir código de esta forma le proporcionará los siguientes beneficios: 5 • Crea un solo punto de mantenimiento si se necesita realizar algún cambio. • El mismo código puede accesar múltiples fuentes de datos. • Puede comenzar a codificar aplicaciones cliente/servidor utilizando software de soporte en VFP hasta que se tome una decisión acerca de utilizar MS SQL Server, Oracle, Informix, Sybase o DB/2. Formularios de entrada de datos basadas en vistas. Cuando se trata de formularios de entrada de datos para una aplicación cliente/servidor, puede utilizar ya sea SQL Pass−through (SPT), el cual es sencillamente pasar una cadena de SQL a través de un ODBC a la fuente de datos, o vistas remotas, que significa enunciados SQL almacenados en DBC cuyas tablas temporales resultantes se comportan muy similar a las tablas nativas de VFP. El SPT almacenará su conjunto de resultados en un cursor local, así que Usted tendrá que hacer un ciclo sobre los controles dentro de un formulario y añadir cada uno de ellos individualmente (por ejemplo, thisform.txtCustomer.Value = SQLResult.customer). Claro, actualizar el servidor requeriría que Usted creara los enunciados SQL Update, Insert, y Delete apropiados, sin mencionar la verificación de contención multiusuario que debe ser codificada también, una tarea algo tediosa y plagada de oportunidades de error. Esta es la manera en que las cosas son usualmente hechas en Visual Basic (así que Usted sabe que debe de haber una mejor manera con VFP <risa>). Las vistas remotas, por otro lado, son increíblemente sencillas de usar. Todo lo que necesita hacer es crear una vista con la misma estructura que la tabla subyacente, abrir la vista, (realizar el SQL Select), manipular la tabla temporal local que se crea de la misma forma en que lo haría con una tabla nativa de FoxPro, luego hacer un TableUpdate. El TableUpdate automáticamente hace unos enunciados SQL Update, SQL Insert y SQL Delete por Usted y los pasa al administrador del controlador ODBC en tu estación de trabajo. El administrador del controlador ODBC utiliza el controlador ODBC que Usted ha seleccionado para traducir el SQL en la sintaxis que el software de soporte (back−end) entiende. Es así de sencillo. Yo recomiendo utilizar las vistas remotas para los ofrmularios de entrada de datos por las siguientes razones: • Un conjunto de código. El mismo conjunto de código puede funcionar con tablas VFP o con tablas remotas en un SQL Server. Usted solo tiene que utilizar vistas locales o vistas remotas de acuerdo a las tablas que se utilicen. • Desempeño. De muchas formas, las vistas pueden ser más rápidas que el SPT debido a que TableUpdate (una función de VFP de bajo nivel) crea automáticamente el código SPT por Usted. El tiempo que tomaría crear las cadenas de SQL update por Usted mismo y ejecutar el código muy probablemente sería mayor que el TableUpdate. Si Usted piensa en cuanto es lo que en realidad TableUpdate realiza (registrar la memoria intermedia [buffer] de cambios, determinar el tipo de actualización hecha, leer las propiedades de la vista, construir el lote de enunciados SQL, pasarlos al administrador de manejador ODBC, regresar una bandera de éxito/fracaso y limpiar la memoria intermedia de cambios), estoy seguro de que Usted estará de acuerdo con que puede tener mejor desempeño que el SPT. • Todo lo anterior se puede hacer sin un solo error de sintaxis. • Las propiedades de las vistas ofrecen una funcionalidad adicional sobre el SPT. Por ejemplo, para simular una cuenta de cinco BatchUpdate, se necesitaría concatenar cinco enunciados SQL con punto y comas antes de pasarlos al ODBC. El SPT aún tiene un muy importante lugar en la aplicación cliente/servidor; Discutiré más acerca de esto en el Paso 3. Para mayor información sobre la utilización básica de las vistas locales y remotas, refierase a la VFP Developer's Guide. Todas los comandos relacionados con datos deben de pasar a través de una función. Casi la peor cosa que puede hacer para convertir en difícil el diseño de su aplicación, es codificar directamente (hard−code) comandos de acceso, tales como los enunciados SQL, Zap, Pack, Reindex, Seek y 6 demás. En otras palabras, ¿luce su código como el siguiente? Function PurgeOrders * This function purges (deletes) orders shipped * prior to the date passed. LParameter dShipDate Local lcMsg Select Count(*) as OrderCnt from Orders ; Where ShipDate <= dShipDate Into Cursor tcPurge If _Tally = 0 lcMsg = "There are no orders to purge." Else Delete from Orders Where ShipDate <= dShipDate lcMsg = Trim(Str(_Tally)) + ; " orders were purged from the system." Endif MessageBox(lcMsg) EndFunc Esta clase de enunciados SQL es solo buena para accesar tablas que se pueden encontrar en su ruta de acceso. Dentro del mundo cliente/servidor puede comunicarse con las fuentes de datos por medio de un manejador de conexión (connection handle). Para las vistas, la cláusula de conexión remota <ConnectionName | DataSourceName> del comando Create SQL View habilita que una vista accese datos remotos. Y para el SQL en línea, la función de FoxPro SQLExec() es utilizada para pasar enunciados SQL al servidor por medio de ODBC. Regresa un −1 si hubo un error, 1 en caso de éxito y un 0 si una petición (query) asíncrona está pendiente de completarse aún. Como ejemplo, aquí está como obtener un cursor de órdenes de venta de un cliente dado utilizando SQL Pass−through: llSuccess = SQLExec(goEnv.nHandle, ; "Select * From ORDERS Where Customer = ?cCustomer",; "tcOrders") > 0 Si Usted hubiese empleado una función de envoltura para el enunciado SQL Select dentro de PurgeOrders, entonces podría condicionalmente ejecutarlo contra datos en el SQL Server, datos en el servidor de archivos o incluso datos en la estación de trabajo local. Debe crear clases de las funciones necesarias para hacer el acceso 7 de datos independiente del software de soporte (back−end). Aquí esta la función PurgeOrders reescrita de forma cliente/servidor: Function PurgeOrders * This function purges (deletes) orders shipped * prior to the date passed. LParameter dShipDate Local lcSQLStr lcSQLStr = "Select Count(*) as OrderCnt " + ; "from Orders " + ; "Where ShipDate <= ?dShipDate" This.SQLExecute(lcSQLStr, "tcPurge") If Reccount("tcPurge") = 0 lcMsg = "There are no orders to purge." Else lcSQLStr = "Delete from Orders ; Where ShipDate <= ?dShipDate" This.SQLExecute (lcSQLStr) lcMsg = Trim(Str(Reccount("tcPurge"))) ; + " orders were purged from the system." Endif MessageBox(lcMsg) EndFunc Function SQLExecute * Wrapper for VFP function SQLExec LParameters cExecuteStr, cCursor Local llSuccess cCursor = Iif(PCount() = 1, "SQLResult", cCursor) 8 llSuccess = .T. If goEnv.lRemote llSuccess = (SQLExec(goEnv.nHandle, ; cExecuteStr, cCursor) > 0) Else && local, just macro expand cExecuteStr * Add VFP "Into Cursor..." clause If Upper(Left(cExecuteStr,6)) = "SELECT" cExecuteStr = cExecuteStr + ; " Into Cursor " + cCursor + " NoFilter" Endif * This should be error trapped to return llSuccess &cExecuteStr Endif Return llSuccess EndFunc Notará el uso de Reccount() vs. _Tally. Los cursores SPT no actualizan _TALLY, así que tendrá que olvidarse de utilizar esta variable tan agradable. Sin embargo, no tendrá que preocuparse de que Reccount() contenga los registros eliminados, dado que las fuentes SQL de datos remotas no tienen una eliminación de dos etapas. Y si el software de soporte fuese una base de datos de VFP, la cláusula NoFilter prevendría que VFP creara un filtro en vez de una tabla temporal. La segunda peor cosa que puede hacer para convertir su aplicación en un diseño cliente/servidor difícil es incluir funciones de VFP en sus SQL Selects. Sin embargo, si Usted tiene una envoltura como SQLExecute, tiene la oportunidad de leer el enunciado SQL antes de ejecutarlo y convertirlo a la sintaxis específica del software de soporte. Por ejemplo, la función de SQL Server, Convert() se utiliza para convertir tipos de datos. Así que, Usted probablemente podría imaginarse como se vería el código para encontrar "Str(" o "Val(" en una cadena y hacer una StrTran() para poner la función Convert en lugar de las anteriores. Una última opción es remover la función incluida y hacer el enunciado SQL independiente del software de soporte, luego utilizar la función VFP específica en el cursor local. Utilice procedimientos almacenados o peticiones parametrizadas. En términos de velocidad de ejecución, no hay nada más rápido que un procedimiento almacenado (stored procedure, SP). Dado que son precompilados y viven en el mismo edificio que las tablas, ¿qué podría ser más eficiente?. La forma en que los parámetros necesitan ser pasados al software de soporte es casi siempre diferente, así que, de nuevo, una función de envoltura se vuelve útil aquí. Por ejemplo, tome nota de la sintaxis distinta entre VFP y el SQL Server: 9 SQLExec("usp_GetOrders('Acme')") && VFP SQLExec("usp_GetOrders 'Acme'") && SQL Server Todo lo que Usted necesita para resolver esto es mejorar la función de envoltura SQLExecute para manejar procedimientos almacenados. (Le dejaré esta sencilla tarea a Usted). Si no desea irse por la ruta de los procedimientos almacenados, siempre están las peticiones parametrizadas almacenadas en objetos de negocios. Con las peticiones parametrizadas, no tiene que preocuparse de la sintaxis de llamada de procedimientos almacenados específicos del software de soporte, ni tiene de preocuparse acerca de rescribir los procedimientos almacenados para cada fuente de datos. La disminución de desempeño que se obtiene puede bien valer el incremento en la mantenibilidad. Usted ya ha visto como es que se ve una petición parametrizada; es solo un enunciado SPT: llSuccess = SQLExec(goEnv.nHandle, ; "Select * From Orders Where Customer ; = ?cCustomer", "tcOrders") > 0 Note la sintaxis de la condición de filtrado: Customer = ?cCustomer. La sintaxis es importante debido a que: 1) la mayoría de los software de soporte la soportan; y 2) le evita tener que construir los parámetros del enunciado SQL en una cadena de caracteres específica del software de soporte: Case lSQLServer "...Customer = '" + cCustomer + "' And ; shipdate <= '" + DTOS(dShipdate) + "'" Case VFP "...Customer = '" + cCustomer + "' And ; shipdate <= {" + DTOS(dShipdate) + "}" Otro beneficio es que no se topará con el "error del apóstrofe", que ocurre cuando intenta construir un enunciado SQL como el anterior, pero la variable tiene un apóstrofe en ella (por ejemplo, "Jim's"). Dado que su valor es evaluado antes de que la cadena sea enviada al servidor, terminará con comillas no apareadas correctamente y con un error de tiempo de ejecución. Como puede ver, el uso de "?" puede realmente compactar su código, así que yo recomiendo su utilización ampliamente. Por las anteriores razones, recomiendo SPT en vez de las vistas, siempre esté creando una petición ad hoc y/o no intenta actualizar la fuente de datos. Muchas personas crean vistas para propósitos de reporteo, pero en sistemas grandes, no puedo ver la razón de incurrir en la sobrecarga de utilizar vistas cuando un procedimiento almacenado o un enunciado SPT SELECT puede ser implementado en su lugar. (A menos que deseé exponer una vista de lado del servidor a sus usuarios). El DBC de vistas del lado del cliente se puede volver demasiado sobrecargado con datos que no necesita mantener, dado que no hay propiedades de la vista que realmente necesiten ser establecidas. Encuentre maneras creativas de limitar los conjuntos de resultados. 10 Dentro del paradigma cliente/servidor, los conjuntos de registros son transferidos desde el servidor hacia la estación de trabajo local, por tanto, deben ser lo más pequeños posible para reducir el tráfico en la red. Por lo tanto, de manera algo diferente a como funcionan las funciones de aplicaciones de servidor de archivos VFP, las formas se deben abrir sin datos. Esto además ayudará a mantener una carga ligera causada por las formas, aún y si la cantidad de sus datos crecen más allá de la capacidad de bases de datos de servidores de archivos, como VFP. Y si piensa al respecto, tiene sentido dar a los usuarios solo aquello que buscan. ¿Por qué presentar al usuario una tabla que tiene 10,000 ordenes de venta abiertas cuando solo puede trabajar con un cliente a la vez? Piense que hemos programado así, durante tanto tiempo, debido a que las herramientas de búsqueda incremental nos dotaron de capacidades de navegación rápida, aún entre grandes cantidades de datos. Si bien es cierto que esto es aceptable en sistemas pequeños, la carga se volvería insoportable en una red y su aplicación no escalaría al mismo paso que los datos y los usuarios lo hicieran. Vistas parametrizadas. Así que si su forma se abre sin datos en el mundo cliente/servidor, ¿como le hacemos para obtener datos? Necesita proporcionarle a sus usuarios una herramienta para introducir criterios de filtrado. Estos valores servirán como parámetros de la porción Where del enunciado SQL de la vista. Usted puede proveer esta funcionalidad de muchas formas diferentes, puede elegir entre muy sencillas hasta muy complejas. Una implementación sencilla es tener una forma adherida a una vista parametrizada. Cuando un usuario entra en el modo Find, Usted puede proveer una caja de texto para introducir el valor del parámetro. Por ejemplo: * Create this view when you create the form. Create SQL View vOrders As Select * From Orders Where Customer = ?cCustomer * Open the view (with no data) in the Data Environment * or Load when you run the form. Use vOrders NoData * Get the customer parameter in Find Mode cCustomer = thisform.txtCustomer.Value Requery() && retrieve orders for cCustomer Una segunda técnica que proporcionaría mucha más flexibilidad es proporcionar una herramienta Query−By−Form (QBF). El QBF significa que en el modo Find, Usted puede proveer a los usuarios una manera para que introduzcan criterios de filtrado en cualquier (o solo la mayoría) de los controles de una forma. Luego construya una cadena Where SQL de acuerdo y utilícela de una de dos maneras: 1. Construya un enunciado SPT para crear un conjunto de registros de los campos importantes y el ID único. Construya una vista que esté parametrizada por el ID único. Los usuarios pueden navegar el cursor para encontrar los datos que necesitan y Usted hacer de nuevo la petición de la vista basada en la ID única. 2. Puede también utilizar la cadena SQL generada por la QBF para recrear la definición de vista remota en tiempo real. Dado que su DBC de las vistas está en la estación de trabajo, no hay problemas de contención 11 aquí. (Más de esto en el Paso 5) . Sugerencia: Existen al menos dos herramientas de otras compañías de software disponibles para implementar Query−By−Form en VFP: • QBF Builder • Stonefield Query Manejando los enlaces entre controles y a tablas de búsqueda Este concepto de limitar el conjunto de resultados debería ser aplicado también a los controles. ¿ Qué de bueno tiene limitar las ordenes de venta a un cliente si se van a llenar varias casillas de selección (combo boxes) en la forma con datos de una tabla de búsqueda (look up)?. La lista de selección (list box) de varios miles de registros no tiene lugar en una aplicación cliente/servidor. Hay al menos dos formas de evitar este problema. Una es no utilizar esta clase de controles en tablas grandes y en su lugar utilizar una caja de texto. Luego solo lanzar una forma separada con una tabla cuando el usuario seleccione una tecla de método abreviado (hotkey) definida. Si Usted insiste en utilizar controles con registros múltiples, puede intentar con una segunda técnica. No llene el control hasta que el evento GotFocus sea disparado. Esta técnica de enlace tardío (late binding) le evitará la sobrecarga de llenar controles no utilizados. Puede utilizar una combinación de cualquiera de estas técnicas dependiendo del tamaño anticipado de la tabla de búsqueda y solo controlarla con una propiedad y/o tabla de opciones. La VFP Developer's Guide le ofrece una sugerencia adicional: Hacer copias de las tablas de búsqueda frecuentemente utilizadas a cada estación de trabajo. Como sea, el saber cuando mantener una versión local en sincronía con la versión del servidor me suena como una pesadilla. Por lo tanto, recomiendo una de las opciones anteriores. Formularios Uno a Muchos Los formularios uno a muchos deberían ser manejados como sigue. Cualquiera que sea la técnica que Usted decida utilizar para formularios de tabla única debe ser utilizada para la porción del encabezado de un formulario uno a muchos. Las vistas de las tablas hijas, deben de ser parametrizada con el ID único de la vista madre. Así que cuando Usted navegue de un encabezado a otro, a la vista hija solo necesita ser pedida de nuevo. Al utilizar este método, solo el hijo del encabezado actual necesita ser adquirido localmente. Mantenga las vistas en su propio DBC para tratar software de soporte independientemente. Si, en su diseño de marco de trabajo, las vistas son mantenidas en sus propios DBC, esto hará la transición a otro software de soporte más sencilla. En otras palabras, cuando Usted desea que su aplicación accese a fuentes de datos diferentes, desea mantener la mayor cantidad de piezas de su marco de trabajo intactas. Mantener las vistas en DBCs separados de las tablas (en un escenario donde VFP es el software de soporte) hará el acceso las fuentes de datos remotas una cuestión de accionar un interruptor. La implementación podría ser como sigue: • Cree un directorio llamado \VFPViews, y almacene un DBC de vistas locales en él. Nombre el DBC como AppViews.DBC (\VFPViews\AppViews.DBC). • Cree un directorio separado para cada fuente de datos que Usted desea accesar, manteniendo el mismo nombre del DBC de la vista. En otras palabras el DBC \SSViews\AppViews.DBC contendría vistas 12 que accesan un SQL Server. Dado que frecuentemente existen varios lugares en su código donde se referirá a las vistas de la base de datos, esto evitará que tenga que escribir código condicional para cada software de soporte. • En un archivo de inicialización de su elección, almacene la información tal como el nombre DSN del ODBC, la ruta de directorios donde están almacenadas las vistas, una bandera indicando si accesa datos locales o remotos y si son locales, la ruta de directorios de donde están almacenadas las tablas de VFP. • Establezca la ruta con Set Path To... al directorio de vistas apropiado al iniciar la aplicación. • Si es un software de soporte en VFP, establezca Set Path To al directorio de red donde se almacenan las tablas de VFP. Aunque es cierto que podría añadir el nombre de la base de datos de vistas al archivo de inicialización, frecuentemente esto ya esta codificado por toda la aplicación. El tener DBCs separados de las vistas también mantendrá el tamaño de éstas manejable. Si el software de soporte fuese una base de datos de VFP, combinarlos significaría que se necesitarían almacenar dos veces el doble del número de objetos de bases de datos. Si tal fuera el caso, Modify Database podría tomarnos un tiempo excesivamente largo en un sistema grande. También notará que el DBC de la vista (sin importar si contiene vistas remotas o locales) es mantenido localmente, no en la red para ser compartido por todos los usuarios. Utilizar esta técnica brindará mucha de la flexibilidad específica de usuario, como aprenderá luego en este artículo. Todo este pedazo se siente como si se asumiera un almacenamiento local también, pero no está dicho explícitamente. Utilize Locate en vez de Seek para navegar en datos locales. Dado que todos los datos son accesados a través de enunciados SQL en una aplicación cliente/servidor, Usted pierde la habilidad de utilizar el mejor amigo de todo desarrollador de VFP, Seek. Enjuage esas lágrimas, porque hay una alternativa: Locate. Dado que los conjuntos locales de resultados serán (por definición cliente/servidor) relativamente pequeños, el comando Locate será más que suficiente para mover el puntero de registro en un cursor local o una tabla temporal. Aquí vienen aún mejores noticias. El conjunto de resultados de una vista es de hecho una tabla temporal. Para ver a que me refiero. Abra cualquier vista y verifique el valor que regresa DBF(). Debe de ver algo como C:\WINDOWS\TEMP\76633985.TMP. Así que esta "cosa" de hecho tiene una presencia en el disco. Y en VFP, tiene una presencia en el disco que podemos indexar. Así que, siéntase libre de indexar al cursor de la vista tanto como desee si siente la necesidad de la velocidad. Pero de todas formas, yo me quedaré con Locate en vez de Seek. Dado que Locate es, además, optimizable con Rushmore, el utilizarlo con o sin índice no hará que falle, uno solo será marginalmente más rápido que el otro. Conclusión. Bueno, solo estamos a la mitad, pero desde ahora estoy seguro de que puede ver la luz al fondo del túnel. Las envolturas (wrappers) y las vistas son ciertamente las claves para hacer su código independiente del software de soporte. Mantener estos conceptos en mente en todo momento le ayudará a hacer aplicaciones cliente/servidor flexibles y escalables. El siguiente mes, cubriré seis pasos más y cerraré con un formulario de ejemplo que puede accesar una base de datos VFP tan bien como una base de datos SQL Server. Luego, Usted seguirá por su cuenta. Nos vemos el mes siguiente, y hasta entonces, comience a envolver. Use claves primarias sustitutas para todos las tablas. Incluso antes de que VFP 3.0 nos diera la capacidad de agregar claves primarias a nuestros claves, el uso de un solo campo, o grupo de campos, para identificar de manera única una fila de la tabla ha sido sugerida por 13 muchos como una práctica sana. En aplicaciones con servidor de archivos, no obstante, en muchas situaciones en realidad no había necesidad de hacer esto; era solamente una buena idea. Sin embargo, con vistas, las reglas cambian. Para utilizar vistas (views) actualizables, debe existir un clave primaria en la tabla en la cual se basa la vista (generalmente no actualizo los datos de una vista multitabla). La razón es que debido a que la vista crea un resultado local establecido cuando se abre (USE <NombreVista>), usted necesita algún mecanismo para poder encontrar el registro subyacente en el servidor que está tratando de actualizar. Esto no siempre era necesario en un modelo de servidor de archivos, donde un registro compartido puede ser directamente editado. Para que las actualizaciones funcionen, las vistas requieren que usted establezca la propiedad KeyField en tiempo de diseño en el Diseñador de Vistas (View Designer) o con DBSETPROP("Viewname.KeyField1", "Field", "KeyField", .T.) o establezca la propiedad KeyFieldList en el cursor abierto en tiempo de ejecución usando CURSORSETPROP("KeyFieldList", <lista delimitada por comas de los campos de la clave primaria>). En cualquier, la vista utilizará los campos de clave (key fields) para encontrar el registro en el servidor en el que usted ha ejecutado SQL Update o SQL Delete. Las claves también serán utilizadas para detectar los conflictos de la actualización, reportar un error si alguien ha borrado el registro que usted está intentando actualizar o ha cambiado el clave. Aparte de ser una necesidad para las vistas actualizables, agregar claves primarias a sus tablas también hace que solicitar un registro en particular sea más simple. Usted descubrirá que en el mundo cliente/servidor necesitará a menudo esa capacidad. El argumento para los claves sustitutas. Si usted tiene campos múltiples que conforman sus claves primarias, sugiero utilizar además una clave substituta generada por el sistema. Al tener este campo, típicamente de tipo entero, le dará el lujo de una identificación única de la fila que le dará gusto tener. Además, para las tablas hijas Usted querrá agregar una campo entero adicional como clave externa (foreign). Usted lo llenaría con la clave primaria sustituta del padre (parent) antes de grabar al hijo (child). Las ventajas de las claves sustitutas incluyen uniones (joins) más rápidas, actualizaciones más rápidas, eliminaciones más rápidas y la recuperación simplificada de los hijos de un padre. La única desventaja está en generarlas. Yo utilizo una tabla de sistema de números consecutivos, contiene una fila por la tabla en la aplicación, pero esto puede causar problemas de conflicto multiusuario si no tiene cuidado. Debido a que estas claves realmente no tienen significado (por eso también se llaman claves abstractas), usted no debe tratar de obtener el siguiente número secuencial del servidor mientras que está a mitad de una transacción. Esto puede crear demasiado conflicto en la tabla del sistema cuando usted necesita las claves primarias para una tabla muy activa. Es mejor conseguir la clave exterior de la transacción y arriesgarse a perderla si una actualización falla. Otra idea puede ser utilizar una de las técnicas para generar una identificación única localmente, eliminando el viaje al servidor y cualquier conflicto multiusuario posible. Igual como cuando nunca he dejado que se vaya ningún contratista de mi casa sin haberme recomendado a un buen reparador de techos, nunca dejo que una conversación con un desarrollador termine sin preguntar por su técnica para la generación de identificaciones únicas. Dos interesantes que he escuchado que usan la generación del lado del cliente son: • 1. Utilice un GUID. Son de 26 caracteres de largo, (supuestamente) únicas en todo el mundo y rápidas para generarse. No son hermosas, pero valen la pena. • 2. Consiga una identificación única de una tabla del sistema del lado del servidor al iniciar la aplicación y después utilice una propiedad local de la aplicación como un contador. Concatene los dos para generar una cadena alfanumérica única. Aunque estoy simplificando un poco, creo que esta 14 técnica es utilizada en Code Book. Agregue una columna de tiempo a cada tabla. Las aplicaciones cliente servidor utilizan casi exclusivamente un bloqueo optimista (optimistic locking). Debido a eso, la verificación de conflictos multiusuario se convierte en más que un desafío de implementar. Debe recordarse continuamente mientras codifica: "quizás no tengo la última versión de estos registros". Aunque la clave primaria se utiliza para detectar conflictos de actualización, su capacidad se limita a detectar si otro usuario ha suprimido el registro que usted está intentando actualizar o si ha cambiado la clave. La clave primaria no le ayudará a determinar si otro usuario a hecho los cambios a los campos que no son parte de la clave antes de hacer efectivos sus cambios. Típicamente, las aplicaciones cliente/servidor utilizan una cierta forma de columna con una marca de tiempo (timestamp), una que se modifica como parte de cada actualización, para indicar la última versión de un registro. (Digo "una cierta forma de marca de tiempo" porque el tipo de dato puede ser casi cualquier cosa). Esta columna entonces es revisada en el servidor durante cada actualización para determinar si está en conflicto con la versión local de la estación de trabajo. Por ejemplo, una vista de VFP pudo generar el siguiente estatuto SQL después de actualizar la vista del cliente: Replace state With 'NY' For city = 'New York' In vcustomers TableUpdate(.T., .F., "vcustomers"): * This is what is auto−generated by the view * and passed to the server. * There would be one of these per updated record. Update customers Set state = 'NY' ; Where cust_pkey = ?vcustomers.cust_pkey and ; timestamp = ?vcustomers.timestamp Si este estatuto de actualización SQL falla con un error de conflicto de actualización (Update Conflict), usted puede reportar a su usuario que alguien modificó este registro mientras lo editaba. Lo que haga después depende de que tan amable sea. En última instancia, o debe hacer una solicitud (query) de nuevo o puede intentar enviar un TableUpdate otra vez con el parámetro Force activado (que no se recomienda a menos que proporcione al usuario una comparación campo por campo de los cambios). La propiedad de la vista, WhereType, es responsable de la verificación de conflictos multiusuarios. Usted tiene cuatro opciones para determinar qué campos son comparados cuando se genera la porción de la verificación de conflictos del estatuto SQL. Key and Modified Fields (clave y campos modificados) y Key and Updatable Fields (clave y campos actualizables) suenan como funciones poderosas, pero siento que en la mayoría de los casos estas opciones son demasiado peligrosas como para implementarse. (No me gustaría que el usuario 1 y el usuario 2 actualizaran dos columnas diferentes del mismo registro sin que cada uno conociera primero los cambios del otro). Esto nos deja Key Fields Only (campos de clave) y Key and Timestamp (clave y marca de tiempo). Key and Timestamp solamente es soportada si su base de datos soporta una columna de tipo marca de tiempo 15 (timestamp). Esta columna es modificada automáticamente por el servidor en cada actualización. La aplicación cliente no mantiene este campo, la vista solamente incluirá la columna en la verificación de conflictos. El SQL Server tiene soporte para una columna de marca de tiempo. De hecho, el asistente SQL Server Upsizing Wizard tiene una opción para crear una columna de marca de tiempo para cada tabla. La columna no es realmente de tipo de dato datetime, sino más bien una cadena única generada por el sistema. La belleza de ella es que el servidor la maneja automáticamente y que es mucho más exacta que el tipo de dato datetime (nunca tendrá que preocuparse por dos usuarios actualizando a la misma hora exacta). Algunos comportamientos de los que hay que estar enterados al usar la columna de marca de tiempo del SQL Server: Si usted vuelve a hacer la petición (query) después grabar, la columna de marca de tiempo en el cursor de su vista local estará actualizada con el valor actual en el servidor. Así, los intentos subsecuentes de grabar el mismo registro fallarán con un error de conflicto de actualización (Update Conflict) cuando se comparen las dos marcas de tiempo. Soluciones para las limitaciones del asistente Upsizing Wizard: El asistente de SQL Server Upsizing Wizard tiene muchos problemas que lo hacen, en mi opinión, casi inutilizable. La mayoría de la gente que conozco ha tenido que escribir sus propias herramientas para resolver las limitaciones. Una virtud que lo salva es que el código fuente de todos los asistentes y generadores ahora viene con la versión 6.0 de VFP. No es realmente tan difícil modificar el código para superar sus limitaciones. El código fuente está situado en \<localización de VFP 6.0>\Tools\Xsource\Xsource.zip. Así que la clave y la marca de tiempo suenan excelentes para el SQL Server, pero ¿qué hay sobre los otros sistemas de soporte (back−ends) que no soportan una columna de marca de tiempo? ¿Y cómo puede mi prototipo con vistas locales de una base de datos de VFP funcionar con el mismo conjunto del código? Mi solución es agregar mi propia columna de marca de tiempo y mantenerla del lado desde el cliente o desde un disparador de actualización del lado del servidor. Entonces convierte el campo en un KeyField (campo de clave) (véase el paso 7) y utiliza Key Fields Only WhereType. Así que su KeyFieldList podría ser cust_pkey y timestamp. (Note que los campos en la propiedad de KeyField o KeyFieldList de una vista no tienen que ser claves primarias o candidatas). Usted puede utilizar una columna de tipo datetime para este propósito. Sin embargo, la columna datetime de VFP tiene precisión de un segundo solamente, así que es posible que dos usuarios puedan actualizar el mismo registro dentro del mismo segundo. Si usted siente que esto es un problema potencial para su aplicación, utilice otra técnica para generar un número o cadena única. Yo utilizaba esta solución hasta que empecé a usar SQL Server e incluí su columna de marca de tiempo. (Dado que usted no tiene ningún control sobre este nombre de campo, puede llamar su columna de marca de tiempo en VFP algo diferente para que no haya conflicto cuando cambie a SQL Server). Entonces puede simplemente cambiar el WhereType a Key and Timestamp (clave y marca de tiempo) y ya está listo. Vida sin fechas. Era un poco inquietante, bueno, quizá era más como una sacudida repentina, descubrir que el SQL Server, como la mayoría de las bases de datos del grandes, no soporta un tipo de dato para fechas (date). Debe utilizar un campo de tipo datetime. Si usted está convirtiendo una aplicación existente de VFP, que utiliza campos de fecha (date), los problemas podrían ser muy extensos. Realmente depende de cómo utiliza las fechas su aplicación y de cuánto le importa ver 12:00:00 al final de sus campos de la fecha. He resumido los problemas a los que me he enfrentado así como sus posibles soluciones. 16 Los controles limitan hasta los campos de la fecha. En los casos donde es simplemente inapropiado mostrar la porción de la hora de un campo del datetime, obviamente usted tiene que realizar alguna clase de truco para perder la hora predeterminada de 12:00:00 AM. Tiene varias opciones. Usted podría utilizar DBSetProp para cambiar el datatype de la definición de la vista de datetime a date. (Como se mencionaba en la parte 1, teniendo un DBC local de vistas elimina cualquier problema de conflicto multiusuarios). Create SQL View vOrder Remote Connection ; Remote1 As Select * from orders DBSetProp("vOrders.orderdate", ; "Field", "DataType", "D(8)") Use vOrders Hacerlo así truncará no sólo la porción del tiempo, sino que también le proporciona un campo de tipo fecha (date) de VFP en su vista, permitiendo a su código específico de fechas funcionar sin cambiarlo. Y afortunadamente, cuando usted ejecuta una TableUpdate en una vista con un campo del tipo fecha en él, se le anexa una hora predeterminada de 12:00:00:00 AM automáticamente en el SQL Server... sin error. Si usted tiene controles que no estén enlazados a una vista sino a un cursor de solo lectura SQL Pass−through (SPT), puede utilizar la función de conversión de tipos de datos del software de soporte (back−end) para hacer el truncamiento. Aquí está un estatuto SQL que se podría pasarse al SQL Server que haría exactamente eso: SELECT orderid, ; customerid, ; CONVERT(char(10), orderdate, 101) as orderdate ; from orders Observe que 101 refiere al argumento opcional de estilo de la función del CONVERT. 1 indica formato americano, mm/dd/yy. Luego le suma 100 si desea el siglo incluido. Para hacer que la función CONVERT funcione para datos locales también, usted puede crear un procedimiento almacenado (stored procedure) llamado "Convert" en la base de datos de vistas. Haga que acepte los tres argumentos de la función de CONVERT del SQL Server y, si se le pasa un datetime, que regrese SubStr(TToC(pDateTimeField), 1, 10). Usted también necesitará escribir un procedimiento almacenado Char para manejar el tipo de dato de la función del SQL Server, char(10). Los campos datetime no pueden estar "vacíos". Como si perder los campos de fecha no fuera ya bastante malo, usted también debe enfrentarse con el hecho de que los campos datetime no pueden estar vacíos en la mayoría de las bases de datos. (Hay un mundo completo de funcionalidad limitada más allá de FoxPro, ¿verdad?) Las columnas datetime deben ser llenadas por una fecha y hora válidas o ser nulas (Null). Si usted permite que sus campos datetime acepten Null, solucionará rápidamente el problema de la interfase de usuario pero probablemente generará otros. Podría experimentar resultados inesperados al introducir ciegamente Nulls. (Por ejemplo, la comparación de dos fechas donde una es nula devuelve Null, Empty (FechaNula) devuelve .F., etcétera). Pero si usted está preparado para enfrentar estos problemas, Null realmente es práctico al presentar datos a los usuarios gracias al comando de ambiente Set NullDisplay to de VFP y a la propiedad NullDisplay de los controles como la caja de texto (text box). 17 Si usted no desea utilizar Nulls, VFP y el SQL Server son en realidad buenos con usted. El enviar una fecha vacía al servidor del SQL no causa ningún error. Sin embargo, el SQL Server creará un datetime predeterminado de 01/01/1900 12:00:000 AM. Ahora, cuando usted solicita esos datos, usted verá 01/01/1900 en el cursor local, suponiendo que usted ha utilizado alguna de las técnicas discutidas anteriormente para truncar la hora. No creo que sea algo tan horrible como para que un usuario no deba verlo, pero si usted ha elegido establecer Set Century Off, 01/01/00 parecerá como el 1 de enero del 2000. (Como si los problemas con el año 2000 no fueran suficientes, ¡al cambiar a SQL Server se crearán problemas del año 1900!) He creado tres posibles soluciones. La primera es agregar un valor predeterminado a todos los campos datetime, uno que ponga en claro que esta es una fecha falsa, como 01/01/9999. (Observe que el tipo de dato datetime tiene un rango de fecha del 1 de enero de 1753 al 31 de diciembre del 9999). Una vez más usted necesitaría establecer Set Century On para distinguirlo del 1999. Una opción similar es agregar un valor predeterminado de la columna a todos los campos datetime que sigan alguna regla de negocios. Por ejemplo, todas las fechas de envío (Ship Date) pueden estar predeterminadamente a ocho semanas después de la fecha de la orden (Order Date), que es un campo requerido. La tercera opción implica más malabarismos. ¿No sería fantástico si hubiera alguna manera de borrar cada fecha igual a {01/01/1900} en el cursor de la vista abierta, no enviar ninguna actualización al servidor y no dejar ningún cambio pendiente? Bien, puede hacerse y es algo como esto: Procedure OpenView LParameters pcView * Retrieve data form server Use (pcView) in 0 * Prevent update statements from going to server CursorSetProp('SendUpdates', .F., pcView) * Strip 01/01/1900 out of this cursor RemoveDefaultDates(pcView) * Remove pending changes/clear the change buffer TableUpdate(.T., .T., pcView) * Restore update capability to view CursorSetProp('SendUpdates', .T., pcView) Por brevedad, no proporcionaré el código para la función RemoveDefaultDates. Pero todo lo que lo hace es un ciclo por todas las columnas con campos del tipo fecha (date) y los substituye por {} si son iguales a {01/01/1900}. Ahora todas las pantallas de entrada de datos se verán y funcionarán como lo hacían cuando el software de soporte (back−end) era solo VFP. Bien, esto funciona para las vistas, pero qué hay sobre los cursores del SPT? Solo necesita ejecutar la función RemoveDefaultDates sobre cualquier cursor que será presentado eventualmente al usuario. 18 Diferencias entre los cursores SPT remotos y cursores SPT locales: Los cursores del SQL Passthrough son de solo lectura cuando están creados de fuentes de datos de un servidor de archivos, pero de son de lectura/escritura cuando están creados de una fuente de datos cliente/servidor. Así pues, si usted está haciendo prototipos localmente, por supuesto necesitará cambiar el cursor a lectura/escritura antes de modificarlo. Matemáticas de fecha. Usted necesita estar atento de cualquier matemática de fechas que pudiera tener en su aplicación. Dado que tiene campos datetime en el servidor (y puede tenerlos en un cursor local también) donde tenía originalmente campos date, pueden ocurrir errores en los cálculos. Por ejemplo: ?Date() + 20 && 03/08/99 + 20 = 03/28/99 ?Datetime() + 20 && 03/08/99 10:35:15 PM + 20 = ; && 03/08/99 10:35:35 PM! * against SQL Server: * ExpectedDate is 14 DAYS after order date Select orderdate, ; Orderdate + 14 as ExpectedDate ; From Orders * against VFP: * ExpectedDate is 14 SECONDS after order date Select orderdate, ; Orderdate + 14 as ExpectedDate ; From Orders Puede ver que el agregar un número a un campo datetime puede tener resultados diferentes basados en el software de soporte (back−end). Es difícil decir si se agregarán segundos o días. Una solución podría ser convertir el número incremental en una función. De ese modo, Usted tiene lo necesario para determinar cual es el software de soporte y proporcionar los cálculos apropiados. Evitar esa sensación de Empty() con los Nulls. Si usted es como yo, quizás usted ha aprovechado la flexibilidad que proporciona la función Empty() de VFP. Empty(eExpression) funciona para cualquier tipo de datos, excepto los objetos. Puede detectar cero, una cadena vacía, una fecha vacía o falso lógico con esta una función sin ni siquiera verificar el tipo de dato. Mala movida. 19 Los problemas pueden ocurrir porque después de cambiar a SQL Server, puede ser que encuentre algunos valores inesperados de funciones y de solicitudes (queries). Puede ser que incluso estén en un tipo de datos diferente al que usted esperaba. Cuando estos valores se evalúan con Empty(), o cualquier función en este caso, podría causar problemas importantes. Por ejemplo: ?Empty({}) && true ?Empty({01/01/1900}) && false ?Empty(Null) && false Usted ha visto el problema con {01/01/1900} en el paso 9, la vida sin fechas, así que sabe lo que puede ocurrir. Borrarlos localmente parecen ser la solución más fácil. ¿Pero qué hay sobre el problema de Null? La mayoría sabe que nulo no es vacío, pero ¿sabía que incluso si usted no tiene ninguna columna en su base de datos que acepte Null, puede ser obtenga valores Null del servidor? Intente esto en el SQL Server: Function GetMaxOrderQty lcSQL = "SELECT MAX(Quantity) AS nMaxQty" +; " FROM [Order Details] " +; " WHERE ProductID = 99 " SQLExec(lcSQL, "cBigOrder") If Reccount("cBigOrder") > 0 lnRetval = 0 Else lnRetval = cBigOrder.nMaxQty Endif Return lnRetval ¿Cuál es el tipo de dato del valor de retorno? Depende. Si se encontraron registros para ProductdID = 99, la respuesta es numérica. Si no hay registros encontrados para ProductdID = 99, la respuesta es Null, a pesar de que la columna de cantidad (Quantity) no soporta valores Null. La razón es que en solicitudes SPT para el SQL Server, usted no obtendrá un cursor resultante sin registros para solicitudes de agregación como esta. Usted obtendrá un registro sin importar si se encontraron coincidencias, a menos que usted tenga una cláusula Group By una columna que no sea de agregación. Y en las columnas de agregación tendrán un valor Null. Eso podría causarle problemas en muchos lugares si no tiene cuidado de evitarlo. En el ejemplo precedente, lnRetval sería nulo porque no hay ningún ProductID = 99. Para evitar obtener valores nulos, usted tiene varias opciones. Por supuesto, encontrar todas las funciones de agregación en su código y hacer un IsNull() el chequeo es una manera. Podría también escribir una envoltura (wrapper) para Empty, quizás IsEmpty, que detecte Null y 01/01/1900 también. Esto se puede utilizar para verificar los 20 valores de diversos tipos de datos sin temor de encontrar resultados inesperados. fialmente, agregando la función Count(), como en Count(*) as Cnt, a todas las solicitudes como éstas, le da una manera común de verificar si hay un valor retorno. La columna Cnt será siempre numérica, así que ahora puede verificar si Cnt > 0 en vez de Reccount() > 0 sin temor a los Null. El caso para los controles no enlazados. Ya ha visto muchos problemas al trabajo con los campos que son de diferentes tipos de datos, dependiendo del software de soporte (back−end). Yo los veo también. Estos problemas hacen que sea muy difícil de escribir un conjunto del código para accesar múltiples fuentes de datos. Obviamente, no todas las aplicaciones requieren tal flexibilidad, pero para los que necesiten o quieran esta funcionalidad, los controles no enlazados (unbound) pueden ser la respuesta. Los controles no enlazados en un formulario no tienen tipo de dato. Usted toma el resultado de una solicitud y esencialmente "pinta" los controles con los valores del contenido del campo. Eso le daría lo necesario para manipular los datos de cualquier manera que necesite antes de presentarlos al usuario. Esto proporciona gran flexibilidad, pero también una tonelada de codificación. ¿Puede usted imaginarse cuánto código tomaría para leer los datos, pasar por todos los controles, para pintar la forma y luego pasar de nuevo por los controles para crear el código SQL que escribirá las actualizaciones al servidor? Eso sin mencionar el tener que agregar el código de validación de tipo de dato a nivel de cada campo que obtiene cuando usa controles enlazados (bound). El desarrollador promedio de FoxPro esta tan acostumbrado a usar los controles enlazados que preferiría masticar vidrio antes que escribir todo ese tedioso código. Debe haber una manera mejor. Si tuviera que vivir mi vida otra vez, cambiaría solamente una cosa: Habría creado otra capa de abstracción de datos. (Bueno, dos cosas: también habría comprado acciones de Microsoft hace 10 años. ¿Quién sabía?) La capa adicional sería implementada en la forma de un cursor de lectura/escritura con la misma estructura y contenido del resultado de la vista. Los controles se pueden estar todavía enlazados a una fuente de datos, mientras que al mismo tiempo usted tiene la capacidad de manipular el cursor de cualquier manera antes de presentarlo. El proceso sería algo como esto: Procedure Load * For this test, open the form with data. Use vOrders In 0 * Get a copy of the result set. Select * from vOrders Into Cursor cOrders1 NoFilter * Make a read−write copy of cOrders1. Use (DBF()) In 0 Again Alias cOrders * Close the temporary read−only cursor. Use In cOrders1 21 * * Now the Init of the controls fire, * which are each bound to cOrders. * cOrders will be the form's master alias. EndProc Ahora solo edite el formulario como de costumbre. En Save, vacíe el cursor editado nuevamente dentro de la vista y ejecute una TableUpdate en ella. Todo el marco de trabajo basado en vistas discutido anteriormente sería aplicable, simplemente está usando una capa adicional más. Y con conjuntos de resultado pequeños, un requisito cliente/servidor, junto con el veloz motor de datos de VFP, el desempeño no debe ser un problema. Al darse usted mismo esta oportunidad de manipular los datos antes de presentarlos, obtiene lo mejor de los controles enlazados y no enlazados. Esto hará posible la adición de nuevas funciones donde los datos subyacentes no coinciden con lo que ve el usuario, por ejemplo, de diferentes monedas, multilingüe y las representaciones carácter de datos como 4 DOZ (4 dozen, cuatro docenas). Creando un formulario que tenga acceso a múltiples software de soporte He proporcionado un formulario que es indicativo de cómo Usted puede diseñar un formulario que necesite tener acceso a fuentes de datos múltiples. Es tosco y sucio, con todo el código de nivel de instancia, para facilitar el aprendizaje, pero siempre podrá mejorarlo una vez que comprenda el diseño. También por simplicidad, he creado solamente un campo de filtro y no he implementado Query by Form. Antes de que usted pueda ejecutar la forma, haga lo siguiente: extraiga los archivos y las carpetas, que están en el archivo o código, en cualquier directorio. Usted encontrará el formulario Orders, los gráficos BMP para completar el formulario y main.prg. El subdirectorio AppData contiene una copia de la base de datos Northwind Traders en formato de VFP. (Utilicé el asistente de transformación de datos del SQL Server para hacer esto). El directorio SSViews contiene una vista remota, vOrders, en un DBC llamado AppViews. De manera similar, el directorio VFPViews contiene una vista local, vOrders, en un DBC llamado AppViews. (Revise el paso 5 en la primera parte del artículo para más información sobre la estructura de archivos y directorios). Ejecute la versión 6.0 de VFP y fije el valor por defecto al directorio donde extrajo los archivos. Tenga el SQL Server 7.0 instalado y ejecutándose. En el panel de control, cree un DSN ODBC de usuario que conecte con la base de datos Northwind Traders y llámelo NORTHWIND_SS7. Para ejecutar el formulario contra el SQL Server, ejecute: MAIN("NORTHWIND_SS7"). Para ejecutar el formulario contra VFP como software de soporte, ejecute: MAIN("NORTHWIND_VFP"). Como usted puede ver en la figura 1, la barra del título indica la fuente de datos. Elija la Find (encontrar), introduzca un cliente como "VINET" y después elija Retrieve (extraer). Los cinco pedidos para este cliente serán extraídos del servidor. Formulario de ejemplo. No se emocione mucho buscando código mágino, porque realmente no hay ninguno. De hecho, no sólo es simple, sino casi idéntico al código que usted vería en un formulario enlazado a tablas intermedias. 22 Las únicas diferencias son la adición de la cláusula NoData del comando del USE en Load, la configuración de la variable privada cCustomer que sirve como el parámetro de la vista y la llamada subsiguiente de la función de Requery que extraerá los datos para el cliente apropiado. Éstos son los únicos dos procedimientos que pudieran generar algún interés remoto: PROCEDURE Load * Important for local views Set Exclusive Off * Need this for table buffering Set MultiLocks On Set Data To AppViews * No data on load, please Use vOrders NoData * Set optimistic table buffering CursorSetProp("Buffering", 5) ENDPROC PROCEDURE cmdfind.Click PRIVATE cCustomerID cCustomerID = "" IF Thisform.PendingChanges() WITH Thisform .Lockscreen = .T. IF Not Thisform.lFindMode thisform.lFindMode = .T. * Change to find mode this.Caption = "\<Retrieve" .SetAll("Enabled", .F., "textbox") .SetAll("Enabled", .F., "_commandbutton") 23 .txtCustomerID.Enabled = .T. .txtCustomerID.Setfocus() ELSE && In find mode, so Retrieve .SetAll("Enabled", .T., "textbox") .SetAll("Enabled", .T., "_commandbutton") this.Caption = "\<Find" .txtCustomerID.Enabled = .F. cCustomerID = .txtCustomerID.Value Requery() thisform.lFindMode = .F. ENDIF .cmdfind.Enabled = .T. .txtOrderID.Enabled = .F. .Refresh() .Lockscreen = .F. ENDWITH ENDIF ENDPROC SQL SQL se ha establecido claramente como el lenguaje estándar de base de datos relaciónales. Hay numerosas versiones de SQL. La versión original se desarrollo en el laboratorio de investigación de San Jose de IBM. Este lenguaje, originalmente denominado Sequel se implemento como parte del proyecto System R , a principios de 1970. El lenguaje Sequel ha evolucionado desde entonces y su nombre ha pasado a ser SQL (Structured Query Lenguaje) lenguaje estructurado de consultas. Actualmente, numerosos productos son compatibles con el lenguaje SQL. • En 1986, ANSI(Instituto Nacional Americano de Normalización) e ISO(Organización Internacional de Normalización) publicaron una norma SQL, denominada SQL−86. • En 1987, IBM publico su propia norma de SQL corporativo, interfaz de base de datos para (Arquitecturas de aplicación a sistemas) SAA−SQL. 24 • En 1989 se público una norma extendida para SQL denominada SQL−89, y actualmente los sistemas de base de datos son normalmente compatibles al menos con las características de SQL−89. La versión actual de la norma SQLANSI/ISO es la norma SQL−92 En este apartado presenta una versión general de SQL basada en las normas SQL−89 y SQL−92. se debe tener en cuenta que algunas implementaciones de SQL pueden ser compatibles solo con SQL−89, no siendo con SQL−92. SQL incorporado permite el acceso a una base de datos desde un programa escrito en un lenguaje anfitrión, pero no proporciona ninguna asistencia en la presentación de los resultados al usuario o en la generación de informes. La mayoría de los productos comerciales relacionados con base de datos proporcionan un lenguaje a la hora de crear la interfaz de usuario y dar formato a los datos para la generación de Informes. Estos lenguajes especiales se denominan lenguajes de cuarta generación. Conclusión. Espero que usted no deje que ninguno de estos problemas lo disuada de migrar a SQL Server. Sí, es mucho trabajo. Pero es más aburrido que complicado. Espero que este artículo le ahorre mucho tiempo al informarle de muchos de los problemas antes de que usted incluso comience. Respecto a tener un conjunto del código que tenga acceso a una base de datos de VFP así como bases de datos cliente/servidor, no la recomiendo para el largo plazo. Hay un demasiado código condicional requerido y duplicación de las herramientas que se deben escribir para asegurarlo. Al final, usted terminará probablemente con un sistema que será ineficaz con todos el software de soporte, aunque siempre podrá superar la situación agregando más hardware. Yo usaría vistas locales solamente si planeara definitivamente cambiar al servidor del SQL, Oracle, etcétera. Después de la etapa de prototipos, recomiendo que usted migre gradualmente su código para trabajar remotamente solamente. La capacidad del SQL Server 7.0 para trabajar sobre los Windows 95/98 y NT y muchas características nuevas hacen la migración de VFP un proceso mucho más sencillo. Puede servir fácilmente como su solamente software de soporte, mientras que usa el motor de los datos de VFP para procesar los datos extraídos del servidor. Juntos, hacen a un equipo perfecto. 25