Mostrando las entradas con la etiqueta SGDB. Mostrar todas las entradas
Mostrando las entradas con la etiqueta SGDB. Mostrar todas las entradas

20 de julio de 2022

Cómo consumir datos de Visual FoxPro en sistemas de 64 bits utilizando un servidor vinculado

Cómo consumir datos de Visual FoxPro en sistemas de 64 bits utilizando un servidor vinculado

Autor: Carlos Alejandro Perez (QEPD) 1965-2021 (Chaco, Argentina)

Publicado en: http://logica10mobile.blogspot.com/2012/05/como-consumir-datos-de-visual-foxpro-en.html

Carlos Alejandro Perez



Introducción

Nos hemos puesto un poco nostálgicos, así que acá volcamos nuestra experiencia reciente en integrar datos de Visual Foxpro (32 bits) en un entorno de IIS de 64 bits, que no podía reducirse a correr 32 bits por condiciones de borde de la instalación.
Como es sabido, los sistemas operativos han ido migrando lentamente hacia los 64 bits. Entre otras ventajas, se tiene mayor direccionamiento en memoria RAM, y un rendimiento más sólido en los entornos de 64 bits, y lo más importante, el foco en los esfuerzos de las grandes compañías, entre otras ventajas. La migración de 16 a 32 bits demoró casi una década, pero de 32 a 64 está ocurriendo muy rápidamente.
En esta coyuntura, la realidad es que todavía existen muchos sistemas desarrollados en torno a Visual Foxpro, que es una aplicación de 32 bits. Éste puede correr en sistemas operativos Windows de 64 bits bajo la emulación WoW64 (Windows on Windows 64), que es esencialmente un conjunto de tres bibliotecas de enlace dinámico. Estas se encargan de colocar una capa muy liviana de adaptación en el sistema de 64 bits, a fin de traducir las llamadas y las estructuras de los sistemas de 32 bits, sin que éstos sufran modificación alguna. Sin embargo, por diseño, los manejadores de dispositivos se manejan de forma distinta. De esta forma, si por ejemplo tenemos una placa de video de tal marca y modelo, y la queremos usar en un sistema de 64 bits, deberemos indefectiblemente conseguir el driver de 64 bits. Como toda máquina virtual, es sumamente difícil comunicarse fuera de ella. En otras palabras, los drivers de 32 bits se podrán ver sólo dentro del ámbito Wow64, es decir: en un sistema operativo de 64 bits, los drivers de 32 serán visibles para los procesos emulados de 32 bits, pero no lo serán desde fuera de ese ámbito.
Ahora, ¿qué tiene que ver esto con Visual FoxPro? Bueno, fundamentalmente, las estrategias de acceso a datos que corren en Windows son basadas en el modelo ODBC (Open Database Connectivity). ODBC, en la implementación para Windows, no es otra cosa que un manejador de impresora modificado para bases de datos, es decir, funciona y se gestiona de manera muy parecida a un device-driver de impresora, y por lo tanto, pueden coexistir drivers de 32 y 64 en un sistema, pero sólo serán visibles dentro de sus ámbitos respectivos de ejecución. El rol del driver ODBC es análogo al manejador de impresora: así como un controlador de una determinada impresora “entiende” el lenguaje y los comandos de la misma (sean ésos PCL, Postcript, etc.), del mismo modo un manejador ODBC “entiende” las particularidades de una determinada base de datos. Y con OLEDB pasa más o menos lo mismo, recordemos que OLE DB es un derivado de ODBC, que introduce el modelo de objetos COM a ODBC, y copia su modelo de doble búfer, etc., con lo cual hay mucha similitud conceptual en cuanto al driver en sí (y no tanta similitud en cómo expone los datos hacia el cliente).

Escenarios posibles entre cliente y servidor

Suponiendo que tenemos un ejecutable de una aplicación cliente que desea acceder a un servidor de datos. Las combinaciones posibles serían estas, siempre hablando de un sistema operativo de 64 bits:
ClienteServidorDriver de 32 bits instaladoDriver de 64 bits instalado
32 bits32 bitsVisible desde el cliente, conectable.Invisible desde el cliente, innecesario para conectarse
32 bits64 bitsVisible desde el cliente, conectable.Invisible desde el cliente, innecesario para conectarse.
64 bits32 bitsInvisible desde el cliente, no se puede conectar.Visible desde el cliente, conectable
64 bits64 bitsInvisible desde el cliente, no se puede conectarVisible desde el cliente, se puede conectar.
Luego, si de VFP y de SQL Server se tratase, podemos asumir como cliente a VFP, y como servidor a SQL Server, y el cuadro de arriba sería válido igualmente. Si nos preguntamos si un cliente de 64 bits podría conectarse a un servidor que corre en 32 bits, la respuesta es: si, en la medida que exista el driver del lado del cliente. Supongamos que existiera un VFP de 64 bits, y se corre una instancia de SQL Server de 32 bits. Como SQL Server es un “servicio de datos” que funciona un un protocolo binario (esto es, basado en sockets con tráfico bajo un modelo propietario) llamado TDS (Tabular Data Stream), tendremos que este servicio es independiente de la versión (32 o 64 bits) que lo produzca: el protocolo es inmutable entre dichas plataformas. Para que lo veamos mejor, un servidor IIS de 32 bits producirá el mismo HTML que uno de 64 bits, ya que el protocolo de comunicación HTTP y el formato del mensaje HTML es conceptualmente siempre el mismo (un documento de texto), independientemente de la plataforma que lo genere. Análogamente, es posible entonces conectarse desde un cliente de 32 a un servidor de 64, siempre y cuando exista el driver de 32 bits para dicho servicio.

Por ejemplo, si queremos gestionar desde un cliente de 64 bits la creación y el mantenimiento de un driver ODBC, sólo veremos los drivers de 64 bits. Hagamos la siguiente prueba: iniciemos el Panel de Control, vayamos a Herramientas administrativas, Configurar orígenes de datos (ODBC). Pidamos crear una nueva conexión de sistema (DSN de sistema), y veremos sólo los drivers de 64 bits que la PC tenga instalados en ese momento:

image
Fig. 1: drivers de 64 bits visibles desde un cliente de 64 bits (Panel de Control)


Ahora bien, sin cambiar de PC, tratemos de hacer lo mismo desde un cliente de 32 bits, por ejemplo, desde el mismo Visual FoxPro, creando una base de datos .DBC y generando una conexión desde allí.

image
Fig. 2: drivers de 32 bits visibles de un cliente de 32 bits (VFP)


Como se aprecia en la figura 2, la cantidad es mayor, y entre ellos , vemos el driver para Visual FoxPro instalado. Tanto en el cliente de 64 bits (Panel de control) como en el cliente de 32 (VFP) podremos gestionar, por ejemplo, la creación de una conexión a SQL Server (independientemente de si el servidor SQL corre en 32 o en 64), porque tenemos ambos drivers de cliente instalados en la PC. Pero no podremos gestionar una conexión a VFP desde un cliente de 64, porque no existe el driver.

El problema con ASP.NET, VS y Windows de 64 bits.

Ahora bien, desde Visual Studio 2010, ¿qué sucede?. Supongamos este escenario:
  • Sistema operativo Windows de 64 bits –> IIS de 64 bits, que contiene:
    • IDE de Visual Studio 2010 convencional, 32 bits, instalado con ajustes por defecto sobre el Windows anterior.
    • Visual FoxPro, 32 bits
    • OLE-DB para Visual FoxPro, 32 bits
El objetivo es construir una página ASP.NET que acceda a los datos de VFP en ese escenario.
El problema es el siguiente:
  • Al programar la aplicación ASP.NET en VS2010, se puede crear sin problemas una conexión VFPOLEDB, ya que el cliente (justamente la IDE de Visual Studio) y el driver OLE-DB son de 32 bits. Al depurar la aplicación ASP.NET, se podrán consumir los datos de VFP con mucha facilidad, por ejemplo, utilizando un control SQLDataSource desde la misma página.
  • Pero al publicar el sitio en IIS, se transfieren los ensamblados a un entorno de ejecución de 64 bits. Las páginas se ejecutan bajo una máquina virtual .NET, que es un proceso de 64 bits. Este CLR de .NET es “incapaz” de registrar el driver de 32 bits OLE-DB para acceder a VFP, y las páginas fallan en IIS dando un error de “dll no registrada”, la de OLE-DB justamente.
Entonces la pregunta es: ¿cómo publicamos una página ASP.NET en un servidor IIS montado en un Windows de 64 bits, que no puede tener compatibilidad de 32 bits por alguna razón, y que la vez consuma datos de Visual FoxPro?
No existe una respuesta fácil. Las posibilidades que exploramos son las siguientes:

Idea centralQué habría que hacerContras
Correr el sitio ASP.NET en IIS habilitado para 32 bits
  • Activar la compatibilidad de 32 bits en IIS de 64 bits, corriendo en una ventana de comandos la siguiente instrucción:


C:\>cscript %SYSTEMDRIVE%\inetpub\adminscripts\adsutil.vbs SET W3SVC/AppPools/Enable32bitAppOnWin64 true


Si bien se activa la compatibilidad de 32 bits en IIS, al mismo tiempo se inhabilitan las de 64 bits. Luego, deberíamos tener todos los sitios corriendo en IIS en modo de 32 bits, aunque el SO de 64 bits (estamos pagando por algo que no usamos).
Instalar dos IIS en el servidor, uno de 32 y otro de 64La segunda instalación de IIS correría en modo de compatibilidad de 32 bits, sólo afectando a las de 64 en esa instancia, y en la otra podremos correr el sitio en 64 bits. Las páginas que acceden a VFP , al estar publicadas en otro servidor, debería correr en otro port HTTP o bien resolver por nombre (porque tienen una misma IP pública) a nivel del IIS principal.No es posible. No se puede instalar dos IIS en un mismo servidor. Si es posible instalar un IIS + Apache, o bien dos instancias de Apache, pero necesitamos IIS para correr ASP.NET.
Implementar un servidor de automatización COM hecho en VFPCrear un objeto COM (servidor de automatización) con VFP, que reciba las consultas SQL de ASP.NET, y devuelva datasets ADO.NET utilizando el comando CursorToXML()No es posible. No se podrá enlazar el COM en el espacio de 64 de bits del IIS en producción.
Implementar un servicio web con VFP y publicarlo en el mismo IIS que publica las páginas ASP.NET.Utilizar el mismo objeto de automatización COM del punto anterior, pero publicarlo como servicio web en el mismo IIS que sirve el sitio principal.No es posible, el COM de VFP de 32 bits no se podrá enlazar al ISAPI de 64 bits.
Ídem anterior, pero publicar el web service de VFP en un servidor Apache de 32 bits.Instalar en paralelo al IIS un servidor Apache de 32 bits, y publicar el servicio web de VFP de 32 bits por allí. Las páginas principales ASP.NET en IIS de 64 bits consultarán al web service local para acceder a los datos VFPTeóricamente posible, pero complicado. Al tener dos HTTP servers bajo una misma IP, uno de los HTTP servers debe trabajar en otro port, o bien transferir el request HTTP por nombre de dominio en el IIS, derivando al Apache de 32 bits las llamadas al servicio web.
Intentar conseguir un driver de 64 bits.Conseguir un driver OLE-DB de 64 bits para VFP provisto por la empresa Sybase, que sirve para su ETL (extracción, transformación y carga).Licencia de Sybase. ¿Dónde se lo consigue sin tener ningún producto Sybase? ¿funciona standalone o necesita un software de Sybase?

La solución del Servidor vinculado (Linked Server)


Tras dos meses de mucho probar y cavilar, se llegó a la conclusión que la mejor solución sería no tocar la infraestructura de IIS 64 bits preexistente, y utilizar el servicio de “servidor encadenado” que tiene SQL Server. El truco consiste en instalar una instancia vacía de SQL Server Express de 32 bits y activar dentro de ella un servidor vinculado o encadenado (linked server), que oficie de enlace entre los datos de VFP y los clientes de 64 bits.

El servidor vinculado o encadenado es esencialmente una pasarela, es decir, una capa de transformación de mensajes. A continuación mostramos un diagrama de bloques conceptual, donde desde un gran sitio ASP.NET en Windows Server de 64 bits, con SQL Server de 64 bits, se accede a VFP del driver VFPOLEDB de 32 bits:


VFP linked server


Figura 3: esquema de bloques de la solución

Microsoft ha incluido, increíblemente podríamos decir, la capacidad de tener servidores encadenados dentro de la instancia Express. El servidor encadenado puede ser cualquiera que soporte un driver OLE-DB para dicha versión (32 o 64 bits). Así, si debemos conectarnos a VFP, deberemos usar VFPOLEDB, que es un driver de 32 bits, y por lo tanto, descargar e instalar la versión de 32 bits de SQL Server Express, descargar e instalar el driver OLE-DB para Visual FoxPro, y estaríamos listos para lograr el acceso desde las páginas ASP.NET corriendo desde un servidor de 64 bits.

Una vez que el servidor encadenado está definido, se puede acceder desde cualquier cliente que tenga un driver de acceso a SQL, sea éste de 32 o de 64 bits. En la figura, el servidor HTTP instalado es IIS7.x, de 64 bits, que aloja un CLR de 64. Dentro de esa máquina virtual CLR, el proveedor administrado ADO.NET para SQL Server será uno regular, nativo, no existirá ningún problema porque sólo se necesitará la visibilidad del servicio de datos. El proveedor administrado hace abstracción de la plataforma del servidor, porque sólo ve los servicios sin importarle los procesos que lo generan. De este modo, desde .NET y con esta solución, se tendrá acceso indistintamente a SQL Server regular, de producción, que puede ser de 64 bits (alternativa de la figura, donde se asume un SQL server prexistente de 64 bits para el sistema principal), y al mismo tiempo, a SQL Server Express de 32 bits que aloja el servidor encadenado, vacío sin tablas nativas.

Configurando el servidor encadenado para que tome el archivo .DBC de Visual FoxPro, se tendrá acceso a todas las tablas de la base de datos Visual Fox, como si estuviesen residiendo de alguna manera en la instancia SQL Server Express.

Configuración paso a paso

1. Descargar e instalar SQL Server Express de 32 bits en el servidor IIS, o bien en algún lugar de la red que sea visible. Luego, hay dar acceso al proceso de SQL Server a la carpeta que contiene las tablas y el contenedor de base de datos de VFP.

Yendo a la carpeta en cuestión, en el explorador de archivos, hacer clic con botón derecho sobre la que contiene las tablas y el contenedor DBC, y seleccionar Propiedades, luego seleccionar la ficha Seguridad. La cuenta que debemos habilitar para acceso no es ninguna de las consabidas Network Service, ni Local System, ni System, sino la denominada MSSQL$SQLEXPRESS, como se muestra en la figura de abajo. El no hacerlo impedirá que se pueda acceder a dicha base de datos.

image

2. Iniciar el administrador de SQL Server, y configurar un servidor encadenado. Para ello abrir el administrador, y hacer clic con botón derecho sobre la instancia correspondiente. Seleccionar el nodo Server Objects, y dentro de él, la opción Linked Servers

image
Fig. 4

3. Verificar drivers. Expandir dicho nodo, y se verá el listado de drivers disponibles en el ámbito de 32 bits. Debe estar visible la opción VFPOLEDB

image
fig. 5

4. Agregar un nuevo servidor vinculado. Hacer clic con botón derecho sobre el nodo Linked Servers para agregar un nuevo un servidor vinculado a los datos VFP. Aparece el siguiente cuadro de diálogo:

image
Fig. 6

Proveer los siguientes datos:

  • Nombre del servidor encadenado: especificar el nombre o etiqueta para el servidor encadenado. Por ejemplo, MIAPPVFP

  • Proveedor: seleccionar OLE DB para Visual FoxPro

  • Nombre del producto: se puede especificar un identificador del producto, en este caso colocar la misma que el nombre del servidor encadenado.

  • Origen de datos: especificar el archivo .DBC de bases de datos de VFP, con el camino completo al directorio. Por ejemplo, C:\MIAPPVFP\DATOS\datos1.dbc

  • Dejar todos los demás campos en blanco, y hacer clic en Aceptar. Asegurarnos que la pantalla de alta quede así:
image
Fig. 7

5. Probar con una nueva consulta. Conectarse a la instancia de SQL Server Express que aloja el servidor encadenado. Recordemos que al no tener bases de datos de usuario de SQL Server, no existirán más que las predeterminadas (master, etc.). Cuando hagamos una consulta al servidor encadenado MIAPPVFP de este ejemplo, no se trabaja contra ninguna base de datos nativa, sino contra la representación interna de la base de datos VFP. En otras palabras, MIAPPVFP es, al mismo tiempo, servidor y base de datos. Debido a esta particularidad, para hacer una selección SELECT a una tabla de VFP, tendremos dos formas de acceder:
  1. Trabajando directamente con el servidor encadenado, armando consultas en Transact-SQL (en la figura 3, el trazo en azul) desde cualquier cliente de datos administrado de .NET. Esta técnica envía strings de consultas a la base de datos SQL Server Express, y el servidor encadenado, a través de VFPOLEDB, realiza la traducción necesaria al dialecto de VFP. Por este motivo, desde ADO.NET no podremos enviar a ejecutar comandos y funciones propias de VFP, como por ejemplo, SELECT DTOS(fecha) AS fecha… , ya que DTOS() no es una función reconocida por T-SQL. El servidor encadenado recibe la petición en T-SQL , la traduce al SQL de Visual FoxPro, ejecuta la consulta, y al recibir la respuesta, acondiciona el conjunto de resultados como si fuese un resultado nativo de SQL Server para enviárselo al cliente que efectuó la petición de datos.

  2. Utilizando la primitiva OPENQUERY de SQL Server, que permite enviar por paso-a-través un string conteniendo comandos nativos de VFP. En este caso, el servidor encadenado pasa a través de sí mismo la consulta sin modificarla, y sólo se encarga de “adaptar” el resultado de respuesta como una respuesta regular de SQL-Server.
5.1 Prueba con sintaxis T-SQL de SQL Server

Para probar si todo funciona bien, escribamos una consulta sencilla sobre una tabla que sabemos preexistente en la DBC de VFP. Nótese la sintaxis de acceso a la tabla. Se utiliza la notación de cuatro segmentos siempre que se acceda de esta manera a un servidor encadenado. Sin embargo, como aquí se trata en realidad de una carpeta y un contenedor de tablas, no existe el concepto de esquema, etc. por lo que estas etiquetas se omiten, dejando solamente los puntos. De esta forma, si el servidor encadenado se llama MIAPPVFP, y la tabla es clientes.dbf, se referencia como FROM miappvfp…clientes (tres puntos), como se muestra en la siguiente figura:

image
fig. 8

5.2 Prueba de consulta con paso-a-través y OPENQUERY

Cuando sea necesario emitir una consulta que sólo puede resolverse a través de VFP, será necesario utilizar el pasfo-a-través. Para lograr esto, será necesario utilizar la función OPENQUERY, en el siguiente formato de consulta:

SELECT <lista de campos> FROM OPENQUERY(<servidor_vinculado>, <string de consulta nativa VFP>)

Donde el <servidor_vinculado> debe especificarse sin comillas, y la consulta VFP nativa debe estar como una constante de caracteres encerrada entre comillas simples:

SELECT nombre,sexo FROM OPENQUERY(miappvfp,’SELECT * FROM clientes’)

image
fig. 9

Con esto configurado, estaremos en condiciones de consumir los datos desde páginas ASP.NET que se publiquen en IIS de 64 bits, pudiendo aprovechar lo que ya sabemos de ADO.NET, etc.

Conexión desde una aplicación de .NET

Para conectarnos desde Visual Studio, el string de conexión a SQL Server es parecido al que usaríamos con tablas regulares, pero en este caso se omite el catálogo inicial o el nombre de base de datos. Por ejemplo:

Data Source=SERVPRINC\SQLEXPRESS;User ID=MiUsuario;Password=MiPassword

Una vez establecido este origen de datos, en nuestro proyecto podremos generar objetos de datos que accedan a la base contenedora de VFP a través del servidor vinculado. Por ejemplo, si utilizásemos el control sqlDataSource para las páginas ASP.NET, podríamos probar una consulta así

image
fig. 10

Donde el secreto está en configurar el datasource SqlDataSource1 de la siguiente manera:

String de conexión: Por ejemplo,  Data Source=SERVPRINC\SQLEXPRESS;User ID=MiUsuario;Password=MiPassword” reemplazando las credenciales por las correspondientes a nuestra instalación.

Comando SELECT: Al configurar el SqlDataSource1, el asistente nos preguntará cómo recuperar la base de datos. Nótese que no nos permite acceder a ninguna tabla ya que no hemos especificado una base de datos por defecto, y la única opción será especificar una consulta SQL. La opción de procedimiento almacenado no está disponible.

image  image
Fig. 11 y 12

Este comando SELECT debe especificarse con sintaxis T-SQL de SQL Server, no con el dialecto de Visual FoxPro. Nótese que hemos definido un parámetro de consulta en la consulta:

SELECT nombre,doc_nro FROM miappvfp…clientes WHERE doc_nro = @doc_nro

Ajuste del parámetro de la consulta: Como el control SqlDataSource1 acepta varias formas de cargar el parámetro antes de ejecutar la consulta, seleccionamos que lo tome del control del formulario txtDoc, que es el cuadro de texto donde el usuario especifica el número de documento a buscar, colocando esos datos en el asistente:

 image
Fig. 13

Prueba de la consulta:

En el siguiente paso del asistente probaremos la consulta. En nuestro caso, a los fines de verificación solamente, hemos creado varias entradas en la tabla (doc_nro no es clave primaria en este test) con un mismo documento, para ver si recupera correctamente:

image   image
Fig. 14 y 15

Aceptemos todo y el SqlDataSource1 quedará configurado.

Ajuste del control GridView: Para el gridview de la página sólo bastará hacer clic en su smarttag (el pequeño triángulo que aparece en la esquina superior derecha al seleccionar el control con el ratón), y elegir como origen de datos al recién configurado SqlDataSource1.

image
fig. 16

Al hacerlo, inmediatamente quedará configurada la visual del gridview. Podemos cambiar la visual del gridview utilizando la opción AutoFormat, etc. y cambiarle la cabecera a cada columna utilizando la opción Edit Columns. A efectos ilustrativos, vemos cómo cambiar la cabecera de la primera columna, de nombre (que trae de la tabla de datos VFP) a Nombre de cliente.

image
fig. 17

Prueba de la página:

Pulsamos F5 y aguardamos hasta que aparezca la página

image
fig. 18

Colocamos el numero de documento y pulsamos consultar. A los breves instantes tendremos la respuesta:

image
fig. 19

Donde los datos provienen de Visual Foxpro. También podríamos haber utilizado un dataset, etc.

Publicar la página en el servidor IIS de 64 bits.

La prueba de fuego será publicar la página al servidor IIS, de 64 bits. Una vez hecho esto, podremos verificarla para ver si funciona correctamente. Esta es una imagen de la misma página instalada en el servidor de producción:

image
fig. 20

Con lo cual, hemos podido publicar los datos de VFP en un entorno ASP.NET con IIS de 64 bits, consumiendo datos de VFP.

Algunos cuidados a tener

1. Campos de tipo fecha: Cuando se utilicen fechas como parámetros, la consulta utilizando sintaxis de T-SQL no es optimizada en el motor VFP si la columna de filtrado es de tipo fecha. Por algún motivo, OLEBD para VFP ejecuta una exploración secuencial completa de toda la tabla, dando como resultado un rendimiento de consulta sub-óptimo que puede llegar incluso a una decena de segundos, algo inaceptable para una página web. En este caso, si necesitamos optimizar la consulta por parámetros de fecha en la tabla de VFP, la única solución posible será utilizar OPENQUERY como se explica en el punto siguiente.

2. Funciones nativas de VFP. Al utilizar sintaxis T-SQL, no podremos enviar una consulta con funciones propias de VFP, por ejemplo, INSTR() o DTOS() no podrían incluirse en la consulta. Como vimos en el paso anterior, tampoco podríamos optimizar una consulta que se haga sobre fechas de VFP. Para estos casos, debermos utilizar OPENQUERY indefectiblemente, a fin de pasar sólo un string de consulta en dialecto VFP que será procesado desde el mismo motor de VFP, sin pasar por ningun proceso de traducción en el servidor encadenado.

Por ejemplo, supongamos que necesitamos optimizar una consulta por la columna de fecha llamada fechaVenta en una tabla llamada VENTAS debemos seguir estos pasos:
  • indexar la tabla de Visual Foxpro con la función DTOS, y hacer la consulta desde ASP.NET con dicha consulta. Suponiendo que la columna sea fechaVenta, debemos indexarla con INDEX ON DTOS(fechaVenta) TAG fV en Visual Foxpro.

  • en ASP.NET, configurar los parámetros para que coincidan con el patrón YYYYMMDD de VFP que devuelve la función DTOS(). Por ejemplo, si tenemos una variable dFecha as date en .NET, podremos obtener su string equivalente al colocar dim cFechaParam as string = dFecha.ToString(“yyyyMMdd”).

  • Armar la consulta en el SQLDataSource, SQLAdapter, etc. especificando que el SelectCommand sea el sigiuente: 
SELECT * FROM OPENQUERY( miappvfp, ‘SELECT fechaVenta, factura, cliente FROM ventas WHERE DTOS(fechaVenta) = ‘”+cFechaParam+”’)

3. Palabras clave en consulta. Si bien VFP nos deja emitir consultas con nombres de campo o tablas que coincidan con las palabras clave, como por ejemplo llegar al extremo de colocar “SELECT select FROM select”  y que ejecute correctamente en VFP, siempre que haya una tabla llamada SELECT con una columna con nombre SELECT, este tipo de consultas será rechazada por VFPOLEDB en el servidor encadenado, dando un error en en la instancia de traducción si utilizamos las consultas T-SQL. Incluso utilizando OPENQUERY, la consulta podría realizarse correctamente en VFP, pero al rearmar la tabla de resultados, VFPOLEDB dará un error y no se podrá obtener el dataset, etc. desde .NET. Por tanto, debemos asegurarnos que, tanto con consultas en T-SQL como con  las especificadas dentro de OPENQUERY, no se utilicen objetos de la base de datos que coincidan con palabras clave de T-SQL.

Conclusión


Con la facilidad de servidor vinculado (linked server) que tiene SQL Server Express, podremos hacer que los procesos de 64 bits consuman los datos de VFP a través de VFPOLEDB. El SQL Server Express no tiene costo, y el driver OLEDB para VFP es de descarga gratuita. Con esta solución, si bien se agrega una instancia más al servidor principal o a algún otro de la red, tenemos la ganancia es que no se debe alterar el mecanismo en .NET de acceso a los datos, porque seguirá siendo el driver nativo que siempre estará disponible, en sistemas de 32 o de 64 bits. Así, uno puede tener toda una instalación de un gran sistema ASP.NET en Windows Server de 64 bits, y aún así acceder a los datos de Visual FoxPro sin necesidad de alterar en nada la programación ni la instalación en producción del sitio.

16 de febrero de 2021

Sentencia JOIN en SQL

 Una manera fácil de entender la clausula JOIN en nuestras sentencias SQL-SELECTs mediante diagramas de Venn.



17 de agosto de 2020

Convertir un cursor SPT en una vista remota

Como convertir un cursor SPT (SQL Pass-Thru) en una vista remota para hacer más fáciles las actualizaciones a los datos.

Una vista remota es un cursor SQL Pass-Thru (SPT) con un "envoltorio de vista" especial que permite que el cursor remoto responda a las funciones TABLEUPDATE(), TABLEREVERT() y REQUERY() de VFP, haciendo más fáciles las actualizaciones a los datos (sin necesidad de escribir tediosas declaraciones SQL INSERT, UPDATE y DELETE).

Sin embargo, algunos desarrolladores VFP sienten que SPT es superior a las vistas remotas, y quieren hacer el trabajo extra necesario para escribir el código de las actualizaciones. Ellos también pueden preferir reducir su mantenimiento adicional, eliminando vistas remotas de un contenedor de base de datos de VFP.

Este artículo demuestra que usted puede usar la función CURSORSETPROP() para convertir un cursor SPT en una vista remota, la cual puede ser actualizada facilmente utilizando la función TABLEUPDATE().

El siguiente PRG demuestra esta técnica, usando la tabla Authors (Autores) de la base de datos Pubs (Publicaciones), contenida en los ejemplos de SQL Server. A fin de ejecutar el PRG con éxito, tendrá que modificar la línea SQLSTRINGCONNECT() para especificar una cadena de conexión que funcione en su computadora.

El procedimiento local RemoteSPTCursor2RemoteView() en el PRG, es una rutina genérica que convierte cualquier cursor SPT en una "vista remota", con lo cual las actualizaciones son fácilmente llevadas a cabo con una simple llamada TABLEUPDATE().

La única diferencia entre un cursor SPT convertido en una vista remota en tiempo de ejecución y una vista remota existente (contenida en una base de datos de VFP) es que no puede hacer un REQUERY() a un cursor SPT convertido en una vista remota. Toda la configuración CURSORGETPROP() funciona, el almacenamiento en buffer (y las funciones permitidas) funcionan, y hasta la función REFRESH() funciona.

Este artículo se aplica a todas las versiones de VFP, pero el siguiente código, requiere VFP 7.0 o superior.

*
* Ejemplo de convertir un cursor SPT en una "vista remota"
*
* El código interesante está en el procedimiento local
* RemoteSPTCursor2RemoteView(), que hace todo el
* trabajo, y que puede modificar para su propio uso
*
CLEAR
LOCAL lnHandle, lnGNM
*
* IMPORTANTE!
* La línea siguiente del código tendrá que ser modificada
* para especificar una cadena válida SQLSTRINGCONNECT()
* para establecee una conexión a la base de datos Pubs
*
WAIT WINDOW "Intentanto conectar a la base de datos Pubs." + CHR(13) + ;
  "Si este intento falla, debera modificar el programa en " + CHR(13) + ;
  "la línea SQLSTRINGCONNECT() para especificar una " + CHR(13) + ;
  "cadena de conexión que funcione en su computadora." NOWAIT
*
lnHandle = SQLSTRINGCONNECT("DRIVER=sql server;SERVER=(local);UID=sa;PWD=;DATABASE=Pubs")
*
WAIT CLEAR
IF lnHandle < 1
  MESSAGEBOX("No puede establecer una conexión a la base de datos Pubs en " + ;
    "SQL Server. Modifique la línea SQLSTRINGCONNECT() para especificar " + ;
    "una cadena de conexión que funcione en su computadora.", 16, "Aviso")
  RETURN
ENDIF
IF SQLEXEC(lnHandle,"SELECT * FROM AUTHORS ORDER BY Au_LName") < 0
  MESSAGEBOX("No puede hacer SELECT * FROM AUTHORS", 16, "Aviso")
  SQLDISCONNECT(0)
  RETURN
ENDIF
SELECT SQLResult
*
* Aquí está donde convertimos el cursor SPT en una vista remota
*
IF NOT RemoteSPTCursor2RemoteView("SQLResult", "Authors", "Au_ID", 5)
  MESSAGEBOX("No puede convertir SQLResult en una vista remota.", 16, "Aviso")
  SQLDISCONNECT(0)
  RETURN
ENDIF
WAIT WINDOW "Haga cambios a los datos," + CHR(13) + ;
  "(Insert/Update/Delete)" + CHR(13) + ;
  "y cierre la ventana Examinar" NOWAIT NOCLEAR
BROWSE LAST
WAIT CLEAR
lnGNM = GETNEXTMODIFIED(0,"SQLResult")
IF lnGNM = 0
  MESSAGEBOX("El buffer esta limpio, aparentemente no hizo cambios.", 48, "Aviso")
ELSE
  *
  * El buffer esta 'sucio'
  *
  GOTO (lnGNM)
  MESSAGEBOX('GetNextModified(0,"SQLResult"): ' + ;
    TRANSFORM(GETNEXTMODIFIED(0,"SQLResult")) + CHR(13) + ;
    'GetFldState(-1,"SQLResult"): ' + TRANSFORM(GETFLDSTATE(-1,"SQLResult")) + CHR(13) + ;
    'Presione "OK" para intentar el TABLEUPDATE(.t.,.t.,"SQLResult")', 48, "Aviso")
  IF TABLEUPDATE(.T.,.T.,"SQLResult")
    *
    * Tuvo éxito!
    *
    MESSAGEBOX("Todas las modificaciones se hicieron exitosamente " + ;
      "con TABLEUPDATE() - La ventana Examinar muestra " + ;
      "un nuevo SELECT * FROM AUTHORS.", 48, "Please Note")
    SQLEXEC(lnHandle,"SELECT * FROM AUTHORS ORDER BY Au_LName")
    WAIT WINDOW "Nuevo " + CHR(13) + "SELECT * FROM AUTHORS" + CHR(13) + ;
      "conteniendo cualquier cambio " + CHR(13) + "que Ud. hizo." NOWAIT NOCLEAR
    BROWSE LAST
    WAIT CLEAR
  ELSE
    *
    * Falló
    *
    LOCAL laError[1]
    AERROR(laError)   &&& laError[1] = 1526
    MESSAGEBOX("El TABLEUPDATE() falló porque " + ;
      TRANSFORM(laError[2]) + "/" + TRANSFORM(laError[3]), 16, "Aviso")
  ENDIF
ENDIF
SQLDISCONNECT(0)
RETURN
*
* --
*
PROCEDURE RemoteSPTCursor2RemoteView
  *
  * Convierte un cursor SPT en un vista remota actualizable
  *
  *  lParameters
  *
  *   tcCursorAlias (R) Alias del cursor SPT
  *   tcTableName (R) Nombre de la tabla remota de la cual 
  *                   tcCursorAlias fue recuperado
  *   tcPKFieldName (R) Nombre del campo en tcCursorAlias 
  *                     que es la llave (primaria)
  *   tnBuffering (O) Especifica el modo de almacenamiento de buffer 
  *                   para tcCursorAlias, 
  *                   por defecto 3 - Optimista de Tabla
  *   tnWhereType (O) Especifica la propiedad WhereType, 
  *                   por defecto 3 - Clave y Modificado
  *   tlExcludePK (O) Bandera lógica que indica si hay que excluir el
  *                   campo de PK del UpdatableFieldList - pasa este
  *                   parámetro como .T. cuando el campo de PK es
  *                   poblado en virtud de ser una columna de Identidad
  *
  LPARAMETERS tcCursorAlias, tcTableName, tcPKFieldName, ;
    tnBuffering, tnWhereType, tlExcludePK
  *
  * propiedades de actualización - UpdateNameList y
  * UpdatableFieldList, igual que una vista remota
  *
  LOCAL lnSelect, lcUpdatableFieldList, lcUpdateNameList, ;
    lcField, xx, lnCount, llSuccess
  lcUpdatableFieldList = SPACE(0)
  lcUpdateNameList = SPACE(0)
  lcField = SPACE(0)
  lnSelect = SELECT(0)
  lnCount = 0
  SELECT (tcCursorAlias)
  *
  * añadir cada campo al UpdateNameList y 
  * las propiedades UpdatableFieldList
  *
  FOR xx = 1 TO FCOUNT()
    lcField = UPPER(ALLTRIM(FIELD(xx)))
    lnCount = lnCount + 1
    lcUpdatableFieldList = lcUpdatableFieldList + ;
      IIF(lnCount=1,SPACE(0),",") + lcField
    lcUpdateNameList = lcUpdateNameList + ;
      IIF(lnCount=1,SPACE(0),",") + lcField + ;
      SPACE(1) + tcTableName + "." + lcField
  ENDFOR
  IF tlExcludePK
    *
    * Cuando las PKs no deben ser generadas a mano 
    * (como cuando el PK es una columna Identity), 
    * el campo PK tiene que ser quitado del 
    * UpdatableFieldList para prevenir un TableUpdate()
    * e intentar actualizar el campo PK, que 
    * causaría un crash 
    *
    *  ... por cualquier razón, el campo de PK 
    *  debe permanecer en el UpdateNameList...
    *
    lcUpdatableFieldList = "," + ALLTRIM(lcUpdatableFieldList) + ","
    lcUpdatableFieldList = STRTRAN(lcUpdatableFieldList, ;
      "," + UPPER(tcPKFieldName) + "," , ",")
    *
    * asegurar que no dejamos una coma durante 
    * el principio o el final de la cadena
    *
    IF LEFTC(lcUpdatableFieldList,1) = ","
      lcUpdatableFieldList = SUBSTRC(lcUpdatableFieldList,2)
    ENDIF
    IF RIGHTC(lcUpdatableFieldList,1) = ","
      lcUpdatableFieldList = LEFTC(lcUpdatableFieldList,LENC(lcUpdatableFieldList)-1)
    ENDIF
  ENDIF
  llSuccess = .F.
  DO CASE
    CASE NOT CURSORSETPROP("KeyFieldList",tcPKFieldName)
      ASSERT .F. MESSAGE PROGRAM() + " no se puede configurar KeyFieldList"
    CASE NOT CURSORSETPROP("Tables",tcTableName)
      ASSERT .F. MESSAGE PROGRAM() + " no se puede configurar Tables"
    CASE NOT CURSORSETPROP("UpdatableFieldList",lcUpdatableFieldList)
      ASSERT .F. MESSAGE PROGRAM() + " no se puede configurar UpdatableFieldList"
    CASE NOT CURSORSETPROP("UpdateNameList",lcUpdateNameList)
      ASSERT .F. MESSAGE PROGRAM() + " no se puede configurar UpdateNameList"
    CASE NOT CURSORSETPROP("WhereType", ;
        IIF(VARTYPE(tnWhereType)="N",tnWhereType,3))
      ASSERT .F. MESSAGE PROGRAM() + " no se puede configurar WhereType"
    CASE NOT CURSORSETPROP("Buffering", ;
        IIF(VARTYPE(tnBuffering)="N",tnBuffering,3))
      ASSERT .F. MESSAGE PROGRAM() + " no se puede configurar Buffering"
    CASE NOT CURSORSETPROP("SendUpdates",.T.)
      ASSERT .F. MESSAGE PROGRAM() + " no se puede configurar SendUpdates"
    OTHERWISE
      llSuccess = .T.
  ENDCASE
  SELECT (lnSelect)
  RETURN llSuccess
ENDPROC

VFP Tips & Tricks - Drew Speedie

21 de septiembre de 2019

Almacenar objetos grandes en la base de datos

Ejemplo de como subir archivos en una base de datos, en este caso PostgreSQL.

Muchas veces nos encontramos con la necesidad de guardar archivos en el servidor para disponibilidad de los usuarios de nuestra aplicación.

La acostumbrada ruta aleatoria del archivo en una base de datos y su posterior copiado en el mismo muchas veces no sera aplicable mas aun cuando nuestra base de datos se encuentra en un servidor remoto donde no disponemos de accesos a sus recursos

Aquí es cuando surge la necesidad de cambiar la estrategia de almacenamiento de estos objetos grandes en campos de la base de datos el ejemplo utiliza un campo tipo text equivalente a un campo memo de una dbf.

Lo primero es convertir nuestro archivo a base 64 para poder almacenarlo y lo hacemos con las siguientes lineas :

nfile = GETFILE()
** convertimos a base 64
wbase64 = strconv(filetostr(nfile),13)
** almacenamos en la base de datos
sqlexec(1,"insert into sistema.multimedia (objeto,nombre) values (? wbase64,?justfname(nfile))","rta")
** para descargar el archivo usaramos la siguiente sintaxis
sqlexec(1,"select * from sistema.multimedia where ide=1","rta")
_rutarchivo = SYS(5) + "\" + ALLTRIM(rta.nombre)
sele rta
strtofile(STRCONV(rta.objeto,14),_rutarchivo)
** y lo podemos abrir con el programa asociado
oShell = CreateObject("WScript.Shell")
oShell.Run(_rutarchivo,2,.f.)

mgx

13 de agosto de 2019

Solucionar el problema del relleno de VarChar

Solucionar el problema del relleno de VarChar

Autor: Mike Lewis
Texto original: Solving the "padded VarChar" problem (http://www.ml-consult.co.uk/foxst-36.htm)
Traducido por: Ana María Bisbé York


¿Cómo evitar los espacios no deseados en las tablas en Bases de datos?

Si utiliza vistas remotas para actualizar datos en bases de datos remotas, probablemente haya sufrido el problema del relleno en campos VarChar.

Cuando Visual FoxPro lee datos remotos (como puede ser SQL Server u Oracle) a una vista remota, convierte cualquier columna VarChar en un campo caracteres. Cada campo se rellena con espacios para llenar el mayor ancho. Si luego utiliza esa vista remota para actualizar el servidor, los espacios agregados se envían de regreso. El servidor almacena espacios en blanco extras en las tablas, lo que impide aprovecharse de los beneficios de datos VarChar.

En VFP 7.0 y antes, no había mucho que se pudiera hacer. De hecho, esta era una de las razones por las que los desarrolladores evitaran el uso de vistas remotas al actualizar datos, prefiriendo en su lugar utilizar comandos UPDATE y DELETE directamente vía SQL pass-through. VFP 8.0 proporciona un tratamiento alternativo con el CursorAdapter. Al agregar la función Trim en la propiedad ConversionFunc del CursorAdapter, puede eliminar los espacios extras. Pero el paso de vistas remotas a CursorAdapters podría involucrar bastante trabajo.

Una mejor solución

VFP 9.0 ofrece una solución mucho mejor. A diferencia de versiones anteriores, la versión 9.0 soporta el tipo de dato VarChar en vistas remotas. Si ambos campos, los de Vistas remotas y los del servidor son VarChar, los datos no se van a rellenar con espacios, entonces, no surgirá el problema.

Sin embargo, esto no ocurre automáticamente. De forma predeterminada, cualquier columna VarChar en el servidor se va a corresponder con un campo de caracteres en las vistas, como en versiones anteriores. Para poder aprovechar las ventajas del nuevo tipo de dato VarChar, hay que cambiar explícitamente el tipo de dato en la vista.
Si está comenzando una aplicación nueva y no tiene creadas sus vistas remotas, está de suerte. Todo lo que necesita hacer es ejecutar el siguiente comando antes de crear las vistas:

CURSORSETPROP("MapVarChar", .T., 0) 

Esto dice a VFP que haga corresponder columnas VarChar con campos VarChar del servidor. Al pasar 0 como tercer parámetro, se estipula que esta configuración se aplica a todas las vistas creadas en la sesión actual. Esto sólo afecta a las nuevas vistas que se creen, cualquier vista creada antes, no se verá afectada. Esta configuración no es persistente, asegúrese de ejecutar el comando anterior antes de crear vistas en la sesión actual.

Hacerlo retrospectivamente

Si ha creado sus vistas remotas, será necesario alterar el tipo de dato cada uno de los campos relevantes. Una vía de hacerlo es desde dentro del diseñador de vistas. Desde el menú Consultas, abra la ventana View SQL (Ver SQL). Verá la sentencia SQL que define la vista, seguida de una serie de llamadas DBSETPROP(). Este va a incluir, por cada uno de los campos de la vista, una línea de código que establece el tipo de dato del campo. Por ejemplo:

DBSetProp(ThisView+".company","Field","DataType","C(40)")

El C(40) en este ejemplo indica un campo de caracteres con ancho fijo, igual a 40. Para convertirlo en un campo VarChar, simplemente cambie la C por una V. Repita este proceso para todos los campos que quiere que se correspondan con VarChar, luego guarde la vista y cierre el diseñador. Los cambios serán efectivos la próxima vez que se abra la vista.

Alternativamente, puede utilizar la ventana comandos para hacer este trabajo. En este caso escriba un comando similar al del ejemplo anterior; pero con el nombre de la vista precediendo al nombre de la columna, en lugar de ThisView. Nuevamente cambie el tipo de datos de C a V:

DBSetProp("Customer.company","Field","DataType","V(40)")

Repita este proceso para cada campo que desee corresponder en cada uno de las vistas remotas. Como antes, los cambios van a tener efecto, la próxima vez que se abra la vista.

Mike Lewis Consultants Ltd. Febrero 2005

9 de julio de 2019

Transacciones de usuarios en base de datos

Tuve la necesidad de crear una solución para ver las transacciones que podrían hacer los usuarios, debido a que trabajo con bases e datos de foxpro(DBC) , y se trataba de no meter mas código o funciones en el mismo sistema realizado, si no que el proceso fuera transparente, osea se trata de escribir un código en los procedimientos almacenados de la DBC y esto si crear programas.

1 - Abrir la base de datos a usar

Crear una tabla con con la siguiente información en la base de datos abierta

NOMBRE: HISTORIAL.DBF

Campo  Campo Nombre  Tipo       Ancho  
1      USUARIO       Caracter   20     
2      TIPO          Caracter   20     
3      FECHA         DateTime   8      
4      TABLA         Caracter   30     
5      EQUIPO        Caracter   50     
6      OBSERVA       Memo       4      

Los indices de la tabla pueden ser creados a su consideración para generar reportes o métodos de consulta propios

2 - Ir a las propiedades de la base de datos y activar la casilla de verificación "SET EVENTS ON " y despues dar click a el botón "EDIT CODE", después insertar el siguiente código:

PROCEDURE Hsts(clTipo)
  LOCAL clobser
  STORE SPACE(0) TO clObser,clObservaciones,clValor,clDatos
  IF !TYPE("cp_login")="C"
    cp_login="DESCONOCIDO"
  ENDIF
  clAlias=Alias()
  If Empty("clAlias")
    Return
  Endif
  Select (clAlias)
  clRutaHistorico=ADDBS(justpath(CURSORGETPROP("Database")))+"HISTORIAL.DBF"
  USE IN (SELECT("Historial_cfg"))
  nl_error=0
  ON ERROR nl_error=1
  USE (clRutaHistorico) IN 0 SHARED AGAIN Alias Historial_cfg
  ON ERROR
  IF nl_error=0
    clObser="Campos Modificados"+CHR(13)
    FOR Ind=1 TO FCOUNT(clAlias)
      clObser=clObser+"  "+FIELD(Ind)+"=  "
      clValor=ALLTRIM(clAlias)+"."+FIELD(Ind)
      DO Case
        CASE VARTYPE(&clValor) = "N"
          clDatos=STR(&clValor,16,2)
        CASE VARTYPE(&clValor) = "C"
          clDatos=&clValor
        CASE VARTYPE(&clValor) = "D"
          clDatos=DTOC(&clValor)
        CASE VARTYPE(&clValor) = "T"
          clDatos=TTOC(&clValor)
        OTHERWISE
          clDatos=""
      ENDCASE
      clObser=clObser+clDatos+CHR(13)
    NEXT
    clObservaciones=clObser
    SELECT Historial_cfg
    APPEND BLANK
    Replace Historial_cfg.TIPO WITH clTipo,;
      Historial_cfg.FECHA WITH DATETIME(),;
      Historial_cfg.USUARIO WITH Cp_LOGIN,;
      Historial_cfg.TABLA WITH clAlias,;
      Historial_cfg.EQUIPO WITH LEFT(SYS(0),AT("#",SYS(0))-1),;
      Historial_cfg.OBSERVA WITH clObservaciones
    USE IN (SELECT("Historial_cfg"))
  ENDIF
  IF !EMPTY(clalias)
    SELECT &clAlias
  ENDIF
  ON ERROR
  RETURN
ENDPROC

3 - En la tablas importantes en donde se requiera el registro de transacciones se realizar lo siguiente modificar datos de la tabla, y ir a la pestaña "Table" y en cada Triggers insertar el siguiente código:

Insert Trigger = Hsts("AGREGAR")
update Trigger = Hsts("MODIFICAR")
delete Trigger = Hsts("ELIMINAR")

4 - Listo después de esto entonces cada transacción se estará grabando el la tabla de Historial, solo faltaría hacer un reporte para visualizar información del historial.

24 de junio de 2018

Conocer la fecha en que fue iniciado el servidor de SQLServer

Es posible saber cuándo fué iniciado el SQLServer (o MSDE), a travéz de VFP usando técnicas SPT (SQL Pass Through).

lcServer = "(local)"
TEXT TO lcConnString NOSHOW TEXTMERGE
[DRIVER=SQL Server;SERVER=< < lcServer > >;
DATABASE=tempdb;Network=DBMSSOCN;
Trusted_Connection=Yes]
ENDTEXT
lnHandle = SQLStringConnect(lcConnString)
IF lnHandle > 0
   TEXT TO lcQuery NOSHOW 
      SELECT crdate AS dFecha
        FROM master.dbo.sysdatabases
        WHERE name = 'tempdb' 
   ENDTEXT

   IF SQLExec(lnHandle,lcQuery,"cSQLServer") > 0
        Messagebox("Fecha de Inicio del Servidor:"+cSQLServer.dFecha)
   ELSE
       IF AERROR(laError) > 0
           Messagebox("Error al consultar fecha de Inico de SQLServer"+;
               CHR(13)+"Error:"+laError[2])
       ELSE
           Messagebox("Error inesperado...")
       ENDIF
   ENDIF
ELSE
   IF AERROR(laError) > 0
       Messagebox("Error al intentar conectar"+CHR(13)+;
              "Error:"+laError[2])
   ELSE
          Messagebox("Error inesperado al intentar conectar"+CHR(13)+;
                "Error:"+laError[2])
   ENDIF
ENDIF

Espero que les sea de utilidad.

Espartaco Palma Martínez