viernes, diciembre 02, 2011

EasyMax: problemas de intercalacion (collation) en SQL Server

La librería FASTCALC.LIB utiliza frecuentemente una técnica que consiste en subir un cursor con ciertos datos al servidor en forma de tabla temporal, que luego es usada para filtrar datos específicos de una o mas tablas del sistema.

Sin embargo, cuando sucede que la BD utiliza un collation distinto al collation configurado en el servidor SQL Server (situación que se presenta típicamente cuando se restaura la BD desde un backup hecho en otro servidor que usaba un collation distinto), se produce un error de intercalación al intentar mezclar en un mismo SELECT los datos de la tabla temporal con datos de las tablas de la BD.

Aun peor, el problema no se limita a las funciones de FASTCALC.LIB sino que afecta a cualquier rutina que haga uso de las funciones QGetTempName(), QCursor2Temp() y QCloseTemp() de dicha librería.

El problema se genera en la función QVFP2ODBC(), que es la encargada de tomar un cursor de memoria y subirlo a la BD en forma de tabla temporal.  Al hacer esto, las columnas tipo CHAR o VARCHAR son creadas con el collation por omisión del servidor (digamos MODERN_SPANISH), el cual no necesariamente coincide con el collation usado en la BD actual (ej, LATIN_GENERAL).

La siguiente version de QVFP2ODBC() soluciona este problema instruyendo a SQL Server para que cree las columnas CHAR y VARCHAR de la tabla temporal usando el collation de la BD actual en lugar del collation del servidor, lo cual soluciona el problema al garantizar que tanto la tabla temporal como las tablas relacionadas en la BD actual usan el mismo collation:


* QVFP2ODBC (FASTCALC.LIB)
* Toma un cursor de memoria y lo sube a la BD actual utilizando el collation de la BD
*
#DEFPARAM pcCursor,pcTable,poDB

*-- Se obtienen la estructura del cursor local
*
#defvar aStr,nCount
#dim aStr[1x1]

#dbselect &pcCursor
nCount:=AFIELDS(aStr)

#let pcTable:="#"+pcCursor on vartype(pcTable)<>"C" or empty(pcTable)
#let pcTable:="#"+pcTable on left(pcTable,1)<>"#"
#let poDB:=CURDB on Type("poDB.Name")<>"C"


*-- Se crea el comando CREATE para crear la tabla temporal en SQL
*
#defvar i,cSQL,nResult,cSQLDT,cIL
cSQL:="CREATE TABLE "+pcTable+" ("
cIL:="("
#for i:=1 to nCount
 cSQLDT:=""
 #let cSQLDT:="CHAR("+ALLT(STR(aStr[i,3]))+") COLLATE database_default" on aStr[i,2]="C" and aStr[i,3]<=10
 #let cSQLDT:="VARCHAR("+ALLT(STR(aStr[i,3]))+") COLLATE database_default" on aStr[i,2]="C" and aStr[i,3]>10
 #let cSQLDT:="NUMERIC("+ALLT(STR(aStr[i,3]))+","+ALLT(STR(aStr[i,4]))+")" on aStr[i,2]="N"
 #let cSQLDT:="BIT" on aStr[i,2]="L"
 #let cSQLDT:="TEXT COLLATE database_default" on aStr[i,2]="M"
 #let cSQLDT:="DATETIME" on aStr[i,2] $ "DT"
 #let cSQLDT:="INT" ON aStr[i,2] $ "I"
 #IF EMPTY(cSQLDT)
  MESSAGEBOX("Tipo no reconocido: " + aStr[i,2],0,"QVFP2ODBC")
 #ENDIF
 cSQL:=cSQL + IIF(i>1,",","") + aStr[i,1] + " " + cSQLDT + " NOT NULL"
 cIL:=cIL + IIF(i>1,",","") + "?m."+aStr[i,1]
#endfor
cSQL:=cSQL + ")"
cIL:=cIL + ")"

#defvar nODBCHnd
nODBCHnd:=QGethODBC(poDB)

*-- Se crea la tabla temporal
nResult:=SQLEXEC(nODBCHnd,cSQL)
#IF nResult < 0
 SQLEXEC(nODBCHnd,"DROP TABLE "+pcTable)
 nResult:=SQLEXEC(nODBCHnd,cSQL)
#ENDIF
#If nResult < 0
 Kernel.Err.Capture()
 #return No
#ENDIF



*-- Se carga la data del cursor en la tabla temporal
*
#dbselect &pcCursor
#dbgo top
#dowhile not eof()
 #fieldstovars
 SQLEXEC(nODBCHnd,"INSERT INTO "+pcTable+" VALUES "+cIL)
 #dbskip
#ENDDO


#return Si

viernes, noviembre 18, 2011

VFP: Clausula READWRITE para VFP 6

Como sabemos, los cursores creados por el comando SELECT INTO en VFP son solo lectura. A partir de VFP 7 se incluyo una clausula llamada READWRITE que nos permite indicar si queremos que el cursor sea de lectura-escritura, lo cual es sumamente útil en infinidad de situaciones.

Sin embargo, para los que por algún motivo aun debemos seguir trabajando con versiones anteriores a VFP 7, convertir un cursor a lectura-escritura representa todo un reto.  El truco mas usado para lograr esto en VFP 6 es usar la clausula NOFILTER para asegurarnos que VFP creara un cursor fisico temporal, hacer una copia fisica del cursor temporal con otro nombre, cerrar el cursor original y luego abrir el cursor temporal con el nombre original del cursor:

SELECT * FROM mitabla INTO CURSOR Q1 NOFILTER
SELECT Q1
COPY FILE (DBF("Q1")) TO ("Q1.TMP")
USE IN Q1
SELECT 0
USE ("Q1.TMP") ALIAS Q1

Pero esta solución dista mucho de ser perfecta, especialmente si el cursor original contaba con columnas cuyo nombre superara los 10 caracteres. En estos casos, el nombre de esas columnas era truncado lo cual causaba molestos problemas con las referencias a esas columnas en el código.

Hace un tiempo ayudé a un amigo a encontrar una forma de solventar este problema, y me encontré con que, como siempre, VFP me daba todas las herramientas necesarias para lograrlo; era solo cuestión de ponerlas a trabajar juntas.

En este caso, la ayuda llegó de la mano de los arrays.  Yo sabía desde siempre que VFP contaba con una función que tomaba la estructura de un DBF o cursor y la almacenaba en un array.  Lo que no sabía es que tanto el comando CREATE CURSOR como INSERT INTO contenían la clausula FROM ARRAY.

El resultado?  una función que recibe el nombre de un cursor solo-lectura y lo convierte en lectura-escritura respetando los nombres largos de columna.

Aqui les dejo la función por si les resulta útil (NOTA Nov 19: limpié y mejoré un poco el código, ademas de corregir un problema cuando el cursor original estaba vacío).

* openForUpdate()
* Función que recibe un cursor de S/L y lo convierte en L/E respetando las columnas con nombres largos
*
* Autor: Victor Espina
*
PROCEDURE openForUpdate(pcCursor)
 *
 * Obtenemos el schema del cursor
 LOCAL ARRAY aSchema[1]
 AFIELDS(aSchema, pcCursor)

 * Obtenemos el contenido del cursor
 LOCAL ARRAY aRows[1]
 LOCAL nRowCount
 SELECT * FROM (pcCursor) INTO ARRAY aRows
 nRowCount = _TALLY

 * Cerrar el cursor original, lo recreamos como un cursor de L/E
 * y lo llenamos con los datos originales (si habia)
 SELECT (pcCursor)
 USE
 CREATE CURSOR (pcCursor) FROM ARRAY aSchema
 IF nRowCount > 0 
  INSERT INTO (pcCursor) FROM ARRAY aRows
 ENDIF
 GO TOP 
 *
ENDPROC

martes, noviembre 15, 2011

VFP: Crear un buffer rapidamente

Primero, aclaremos conceptos. Llamo "buffer" a un objeto muy liviano cuya unica función es contener data temporal en forma de propiedades. En muchos sentidos vendria a ser como una versión light de un STRUCT, solo que sin la posibilidad de incluir métodos; solo propiedades.

Por ejemplo, si quiero pasar información sobre una persona de un form a otro, me seria muy útil pasar un solo parámetro que contuviera toda la información de esa persona.  Una forma de lograr esto es crear una clase "Persona" que contenga las propiedades para almacenar la información sobre una persona, pero muchas veces queremos lograr lo mismo sin la sobrecarga de crear una clase que en si misma no hace nada.

Ese objeto "temporal" que sirve para almacenar múltiples informaciones relacionadas, es lo que llamo "buffer".  La función crearBuffer hace justamente eso: crea un buffer con una lista de propiedades dada y, opcionalmente, inicializa esas propiedades con los datos suministrados; todo en una misma instrucción.

Ejemplo:

LOCAL oPersona
oPersona = crearBuffer("nombre,apellido,cedula,fechaNac,cargo","Victor","Espina","12345678",{18-11-1970})

?oPersona.Nombre --> "Victor"
?oPersona.Apellido --> "Espina"


Práctico, cierto?  aqui les dejo el código fuente.

*-- crearBuffer
*   Funcion para crear un buffer de datos e inicializarlo. Compatible con VFP 5 o superior
*
*   Autor: V Espina
*
*   Ejemplo:
*   oBuff = CFDBuffer("Nombre,Apellido","Victor","Espina")
*   ?oBuff.Nombre -> "Victor"
*   ?oBuff.Apellido -> "Espina"
*
PROCEDURE crearBuffer
LPARAMETERS pcItemList,p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16,p17,p18,p19
 *
 LOCAL oBuff,i,cProp
 oBuff=CREATEOBJECT("Custom")

 LOCAL cPropName,uPropValue,nCount
 LOCAL ARRAY aProps[1]
 nCount = ALINES(aProps,STRT(pcItemList,",",CHR(13)+CHR(10)))
 FOR i=1 TO MIN(nCount,20)
  cPropName = aProps[i]
  uPropValue = EVALUATE("P" + ALLTRIM(STR(i - 1)))
  oBuff.AddProperty(cPropName, uPropValue)
 ENDFOR
 
 RETURN oBuff
 *
ENDPROC

lunes, noviembre 07, 2011

VFP: Como crear un dll en .NET y usarlo desde VFP


Alguien una vez dijo que no es cuestion de cambiarse de un lenguaje a otro sino de usar la herramienta adecuada al problema que se desea resolver. C# es excelente para muchas cosas en las que VFP no es tan bueno, por lo que el poder crear un DLL en C# y usarlo en VFP se convierte en algo muy poderoso.

Aqui les dejo todo un conjunto de tips para lograr justamente esto:

1. En tu clase C# debes incluir el namespace System.Runtime.InteropServices;

2. La clase que deseas accesar desde VFP debe estar declarada como public y tener estas directivas:

[ClassInterface(ClassInterfaceType.AutoDual)]
[ProgId("className")]

donde "className" es el nombre OLE de la clase, es decir, el que usaras en el CREATEOBJECT en VFP.

3 Los metodos de la clase que quieras accesar desde VFP deben estar declarados como public y no ser estaticos.

4. Los metodos publicos de la clase no pueden tener sobrecargas, es decir, multiples versiones del mismo metodo con diferentes parametros.

5. Si un metodo devuelve un tipo estructurado, este tipo debe ser creado como class (no como struct) y cumplir con todas las condiciones indicadas en los puntos 1, 2, 3 y 4.

6. Si un metodo devuelve un array de cualquier tipo (ej, string[]), VFP lo recibe como un tipo ARRAY y no como un objeto (como lo es en .NET), por lo que no podremos acceder a las propiedaes del array, tal como Lenght, pero si a sus items:

aItems = myClass.MyMethod()
FOR EACH oItem IN aItems
 ?oItem.Property
ENDFOR

o

aItems = myClass.MyMethod()
FOR i = 1 TO ALEN(aItems,1)
oItem = aItems[i]
 ?oItem.Property
ENDFOR

7. Antes de compilar la clase, debes ir a las propiedades del proyecto, Application, boton Assembly Information y marcar la casilla "Make assembly COM-visible"

8. Debes firmar la DLL. Para esto, sigue los pasos indicados aqui

9. Una vez compilada la dll, la misma debe ser registrada de la siguiente forma antes de poder ser usada en VFP:

Windows 32 bits:
C:\WINDOWS\microsoft.net\framework\v2.0.50727\regasm mylib.dll /register /codebase /tlb

Windows 64 bits
C:\WINDOWS\microsoft.net\framework64\v2.0.50727\regasm mylib.dll /register /codebase /tlb



viernes, octubre 21, 2011

SQL Server: una forma rapida de crear un IN dinamico

Esta es una situación que seguro les ha pasado muchas veces. Tienen un SP que recibe un parametro usado para filtrar sobre una tabla, digamos @status. Normalmente haríamos algo como esto:

SELECT * 
  FROM tabla
 WHERE (@status IS NULL OR status = @status)


Eso nos permitiria filtrar por un status especifico o no filtrar por status, indicando el valor NULL. Pero luego nos topamos con una situación en la que necesitamos filtrar por DOS valores de status distintos, digamos, registros ACTIVOS y ANULADOS. Obviamente nuestra primera intención es hacer:

SELECT * 
  FROM tabla
 WHERE (@status IS NULL OR status IN (@status))


lo cual no es una instrucción valida para SQL Server, sin importar lo logico que se vea. Normalmente la solución a esta situación pasa por la creación de una función que tome un VARCHAR con la lista de valores separados por coma y devuelva un TABLE con los valores ya separados, y luego usamos ese TABLE para hacer un FULL JOIN o un IN (SELECT).

Pero hoy encontre una solución bien sencilla, usando las desconocidas (al menos para mi) capacidades XML de SQL Server. La idea, basicamente, es usar la lista de valores para crear un XML y luego usar ese XML como una fuente datos en un IN (SELECT). Aplicando esto al ejemplo que mencionaba al principio, la cosa quedaria asi:

DECLARE @x XML
SET @x = '' + REPLACE( @status, ',', '') + ''

SELECT * 
  FROM tabla
 WHERE status IN (SELECT x.j.value('.', 'VARCHAR(max)') AS item FROM @x.nodes('//j') x(j))


Simple, cierto?

jueves, octubre 06, 2011

Buen viaje, Steve


Ayer inició su último viaje uno de mis heroes personales. Steve Jobs; su forma de encarar los desafios y su fuerte pasion por el diseño y la funcionalidad, han influenciado mi trabajo y mi forma de hacer las cosas desde que por primera vez tuve la oportunidad de ver de cerca una Macintosh original hace ya mas de 20 años.

El mundo no fué el mismo desde que Steve empezó a imprimir su particular forma de pensar y ver la computación, y definitivamente no será el mismo ahora que se ha ido.

Quiero terminar esta nota con una cita que recopiló mi esposa para mi:

Tres manzanas han cambiado el mundo: la que Eva le ofreció a Adan, la que despertó a Newton y la que sembró Steve Jobs

martes, septiembre 13, 2011

M$: Como instalar MSSMSE en Windows Vista/7

Hoy estaba intentando instalar el SQL Server Managment Studio Express en un Windows 7 64bits cuando, oh sorpresa, el instalador fallo al final sin mas informacion que un error criptico.

Una rapida busqueda en Google me llevo a este link, donde indicaban somo solventar el problema:

1. Vaya al escritorio y cree un nuevo acceso directo
2. Apunte el acceso directo a c:\Windows\SysWow64\cmd.exe
3. Guarde el acceso directo
4. Haga click derecho sobre el acceso directo y seleccione "Ejecutar como Administrador"
5. En la linea de comandos, escriba la ruta y nombre del instalador del MSSMSE, ej: C:\Users\[YOUR NAME]\Downloads\SQLServer2005_SSMSEE_x64.msi

viernes, septiembre 09, 2011

Registrando MSCOMCTL.OCX en ambientes de 64bits

Si se encuentran con el problema de intentar registrar la libreria MSCOMCTL.OCX en un Windows de 64 bits, he aqui como lograrlo:

1. Copie el archivo MSCOMCTL.OCX en la carpeta C:\Windows\System
2. Abra Notepad y escriba: REGSRV32 MSCOMCTL.OCX
3. Guarde el archivo con el nombre "regcomctl.bat" en C:\Windows\System
4. Ubique el archivo C:\Windows\System\regcomctl.bat, haga click derecho sobre el y seleccione "Ejecutar como administrador"

La informacion original la puede conseguir aqui.

viernes, agosto 05, 2011

NETCF: Crear un UserControl que acepte otros controles en tiempo de diseño

Hoy me encontre con un problema que, aunque al principio me confundio bastante, despues logre entender la logica del asunto y la razon de porque lo que queria hacer no iba a funcionar.

El tema es que queria crear un UserControl que me sirviera de container para almacenar otros objetos en un form, muy al estilo de lo que haria un control tipo Panel, pero con la ventaja de estar adaptado a una funcion especifica. Para tal efecto procedi a crear el UserControl, configure su apariencia, tamano, eventos, funcionalidad, etc.

Luego instancie un par de estos nuevos controles en un form de .NET CF y me dedique a poner algunos controles en cada uno de mis controles personalizados. La idea basicamente era que el form mostraria primero uno de los UserControl, conjuntamente con su contenido, y al pulsar un boton en el UserCOntrol1, se activaria el UserControl2 con su contenido particular.

Pero no funciono. Luego de varias pruebas me di cunta que el problema era que los controles que cree "supuestamente" dentro de los UserControls estaban realmente asociados al Form directamente.

Para los que vienen de VFP, es el mismo caso que pasaria si se creara un control basado en la clase Control en lugar de Containar: el control como tal no seria capaz de contener otros objetos en tiempo de diseno, aun cuando la clase Control es en container.

Fue entonces que me di cuenta que lo que yo queria era un control basado en UserControl pero que se comportara como un Panel. Despues de mucho buscar, encontre este post que indicaba como lograrlo.

Basicamente lo que se hace es indicarle al VS que use el Designer asociado a la clase Panel, como Designer de nuestro UserControl. De esta forma VS trata a nuestro control como si fuera un Panel (permitiendole contener otros objetos) pero en tiempo de ejecucion el control se comportara como nuestro UserControl. El truco es incluir el siguiente codigo en el archivo DesignTimeAttributes.xmta:

  <Class Name="SuNameSpace.SuUserControl;>
    <DesktopCompatible>true</DesktopCompatible>
    <Designer>
      <Type>
        Microsoft.CompactFramework.Design.WindowsCE.PanelDesigner, Microsoft.CompactFramework.Design.WindowsCE, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, Custom=null
      </Type>
    </Designer>
  </Class>

viernes, julio 15, 2011

Que pasa cuando metes un iPhone dentro de una guitarra?

Me encontre con esto hoy y me parecio simplemente ESPECTACULAR. Como nota al margen; se ve que el tipo es super fan de Eric Clapton :)




jueves, julio 07, 2011

NETCF: Conectar con un servidor SQL Server remoto

En este blog he publicado varios posts relacionados con los problemas que se presentan a la hora de conectar una aplicacion de Windows Mobile con un servidor SQL Server mediante la red WiFi.

Ahora quisiera anadir uno mas para la lista. Resulta que en muchas redes, debido a alguna configuracion que desconozco, WM es incapaz de resolver un nombre de host, es decir, que si intentamos conectarnos con una cadena como esta:

Server=AdminServer;uid=sa;pwd=sasa;Database=SysData;

WM deberia ser capaz de resolver el nombre AdminServer y obtener su IP real. Como decia, esto no siempre es posible y, cuando WM no puede resolver el nombre, el resultado es el muy conocido error SQL Server Not Found.

Seguramente hay varias formas de resolver esto. En mi caso, opte por intentar resolver el nombre del server y, de ser necesario, solicitar al usuario el IP de dicho server. Con ambos datos, creo una entrada en el archivo Hosts del Windows Mobile a fin de asegurarme que siempre podra resolver el nombre del server, independientemente de la configuracion de la red.

Para hacer esto, necesitamos dos cosas:

a) Dado un nombre de host, como obtener su direccion IP:
using System.Net;
private string resolveHost(string host)
{
  string hostIP = string.Empty;
  try
  {
      IPHostEntry IPHost = Dns.GetHostEntry(host);
      hostIP = IPHost.AddressList[0].ToString();
  }
  catch (System.Net.WebException webEx)
  {
       MessageBox,Show(webEx.Message);
  }
  catch (System.Exception othEx)
  {
       MessageBox.Show(othEx.Message);
  }

  return hostIP;
}

b) Habiendo obtenido el IP, bien sea por resolucion del host o porque el usuario lo indico, procedemos a crear una entrada en el archivo Hosts (que en WindowsMobile se maneja a nivel del Registry):
using Microsoft.Win32;

string hostName = "AdminServer";
string hostIP = "192.168.2.2";
string regKeyName = "HKEY_LOCAL_MACHINE\\Comm\\Tcpip\\Hosts\\" + hostName;
byte[] regKeyValue = System.Net.IPAddress.Parse(hostIP).GetAddressBytes();
Registry.SetValue(regKeyName, "ipaddr", regKeyValue, RegistryValueKind.Binary);


Otro As bajo la manga a la hora de resolver este problema tan molesto.


ODBC: Error raro con Windows 2008 x64

Bueno, la historia es mas o menos así: un sistema hecho en VFP 6, utiliza SQL Server 2008 R2 como motor de datos y ODBC como via de comunicación. Específicamente, estaba usando el driver SQL Server Native Client 10.0 que se instala con el SQL Server 2008.

En las pruebas del programa en mi equipo (Windows XP 32bits) todo funcionaba perfecto. Pero cuando fuimos a instalar en el cliente (Windows 2008 Std x64), empezamos a obtener este error cada vez que el programa intentaba invocar un Stored Procedure:

1526 Connectivity error: Unable to retrieve specific error information. Driver is probably out of resources.

Después de leer varios posts, la única conclusión que pude sacar es que el problema se debía a algún tipo de problema con el driver ODBC de SQL Server, en parte por lo indicado en este post, que aunque específicamente no aplica a este caso particular (yo no estaba pasando ningún parámetro tipo text), ciertamente arrojaba alguna luz sobre el posible origen del problema.

Dado que no tenia muchas mas opciones, decidí intentar usar el antiguo driver SQL Server que usaba para conectarme con SQL Server 2000.... y voila! resulta que con ese driver todo empezó a funcionar como se esperaba.

Quiero llamar la atención sobre el hecho de que el problema se presento SOLAMENTE en el Windows 2008 Std corriendo a 64bits, pues luego hicimos pruebas con un servidor Windows 2008 std pero a 32 bits, usando el driver SQL Server Native Client 10.0, y todo funciono perfectamente.

En fin, para que lo tengan en cuenta si por casualidad le pasa lo mismo.

sábado, mayo 21, 2011

Y el mundo no se acabo... o eso creo

Bueno, ya pasaron las 6pm aquí en Chile y el mundo no se acabo... eso o ninguna de las personas que yo conozco eran puras de corazón, porque aun estamos todos aquí!!!

Como escribió mi esposa: "Hoy a las 6pm Dios vendría a llevarse a los puros de corazón... asi que si estas leyendo esto eres tan mierdita como yo!!"

A ver si esta vez la humanidad aprende de una buena vez a no creer en profecías apocalípticas... aunque algo me dice que el año que viene empezaremos de nuevo con esta cancioncita.



sábado, marzo 26, 2011

VFP: De vuelta a casa

Luego de un par de anos trabajando principalmente con .NET en todos sus sabores (ASP.NET, WinForms, Compact Framework), el mes pasado empece un proyecto que me ha llevado de nuevo a utilizar VFP como ambiente principal de desarrollo.

Y la verdad, ha sido como volver a casa. Una casa renovada, eso si, pues el proyecto esta basado en VFP 9 por lo que he podido hacer uso de varias de las nuevas opciones que vinieron en esa excelente versión.

Paradójicamente, cada vez mas me encuentro a mi mismo aplicando en VFP conceptos que aprendí en .NET y específicamente en C#. En lugar de constantes he simulado enumeraciones usando la clase Empty y la función ADDPROPERTY, en lugar de usar una variable global donde almacenar el handle de conexión devuelto por SQLEXEC() me encontré representando la conexión con una clase llamada SqlConnection. Asi que, cuando antes hacia:

LOCAL nConn,nResult
nConn = 0

TRY
nConn = SQLSTRINGCONNECT(cConnStr)
nResult = SQLEXEC(nConn,cSQL,cCursor)

CATCH TO ex

FINALLY
IF nConn <> 0
SQLDISCONNECT(nConn)
ENDIF

ENDTRY

ahora hago:

LOCAL oConn
oConn = CREATEOBJECT("SqlConnection",cConnStr)

TRY
oConn.Open()
oConn.Send(cSQL,cCursor)

CATCH TO ex

FINALLY
IF oConn.State = connectionState.Opened
oConn.Close()
ENDIF

ENDTRY

Cómico verdad? pero lo cierto es que no solamente yo me siento mas cómodo con la 2da versión, supongo que porque me recuerda a lo que he venido usando en los últimos anos, sino que ademas creo que el código final es mas legible y mucho mas reusable y encapsulado también.

Por cierto, si alguien sabe como simular el concepto de Interfaces en VFP, que me avise, porque esa es una de las cosas de .NET que quisiera tener en VFP !!


VFP + SQL: Cuidado al instalar el OLEDB Provider de VFP

Haciendo algunas pruebas con Linked Servers en SQL Server, necesite instalar el OLE DB Provider de VFP 9.0, el cual descargue directamente desde el sitio oficial de Microsoft.

Sin embargo, luego de haber descargado el instalador y haberlo corrido sin problemas, la opción de OLE DB Provider para VFP no aparecía en la lista de Providers de SQL Server (32 bits).

Luego de varios minutos de búsquedas en San Google, encontré un post donde indicaban que por alguna razón el instalador no registraba la librería correctamente, y que la misma debía ser registrada manualmente para que funcionara (aunque para el autor del post esto no soluciono del todo su problema, pero esa es otra historia).

Así que abrí una sesión de comandos y ejecute:

CD \Windows\System32
REGSVR32 vfpoledb.dll

Y efectivamente el problema se soluciono y la opción apareció en la lista de providers de SQL Server. Asi que ya saben; si necesitan instalar el provider de OLE DB de VFP, luego de instalado deben registrarlo manualmente para que funcione.

Y si alguien tiene idea de porque demonios el instalador de M$ no hace el registro automáticamente como uno esperaría, que por favor nos ilumine con su sabiduría.