martes, 27 de mayo de 2014

TBlueport, componente de conexión Android Bluetooth en Delphi XE5

TBLUEPORT COMPONENT

El TBluePort es un componente que permite la conexión de manera sencilla desde Android a dispositivos Bluetooth en configuración de puerto serial.  Encapsula el API de Android utilizando la librería Androidapi.JNI.BluetoothAdapter publicada por  RedTitan Technology 2013, http://www.delphigear.cn/0/10162/go.aspx

Propiedades

IsEnabled: Campo Booleano que permite determinar si el dispositivo Bluetooth está activo en el sistema, es una propiedad de solo lectura.
Ej.  If BluePort1.IsEnabled then…

PortList: Campo de tipo TStringList, retorna una lista de los dispositivos Bluetooth disponibles, en formato nombre=MAC,  cada elemento de esta lista se puede utilizar como información para asignar el puerto.
Ej.
procedure TForm4.FormCreate(Sender: TObject);
begin
   If BluePort1.IsEnabled then
      ListBox1.Items.text := BluePort1.PortList.text;
end;

Active: Permite conectar o desconectar del puerto previamente seleccionado.
Ej.  BluePort1.Active := True;
Ej.  If BluePort1.Active then…

Port: Especifica el nombre del puerto y la dirección MAC correspondiente al dispositivo bluetooth, el campo es de tipo texto y debe tener la forma  nombrepuerto=MACAddress, en su defecto puede contener solamente la MAC.
Ej.  BluePort1.Port := ‘BOLUTEC=00:15:FF:F3:PE:38

SleepTime: Corresponde al tiempo en milisegundos que espera la multitarea de lectura antes de revisar el Puerto nuevamente.



Eventos:
OnAfterClose: Este evento responde a la acción de desconectar el puerto asignando False a la propiedad Active,  no reconoce la desconexión del puerto por perdida de la conectividad con el dispositivo bluetooth asociado.
Ej.
procedure TForm4.BluePort1AfterClose(Sender: TObject);
begin
   Toast('se ha desconectado de ' + BluePort1.Port + ')');
   BtnConnect.Enabled := Not BluePort1.Active;
   BtnDisconnect.Enabled := BluePort1.Active;
end;

OnAferOpen: El evento se dispara al realizar la conexión y únicamente si la conexión fue exitosa, en este caso la propiedad Active ya se encuentra en True.

Ej.
procedure TForm4.BluePort1AfterOpen(Sender: TObject);
begin
   Toast('se ha conectado a ' + BluePort1.Port + ')');
   BtnConnect.Enabled := Not BluePort1.Active;
   BtnDisconnect.Enabled := BluePort1.Active;

end;

OnBeforeClose:  Evento que responde a la orden de desconectarse,  cambiando la propiedad Active a False.
Ej.
procedure TForm4.BluePort1BeforeClose(Sender: TObject);
begin
   Toast('se está desconectado de ' + BluePort1.Port + ')');
end;

OnBeforeOpen: Evento que responde a la orden de conectarse, cambiando la propiedad Active a True.
Ej.
procedure TForm4.BluePort1BeforeOpen(Sender: TObject);
begin
   Toast('Conectando a ' + BluePort1.Port + ')');
end;

OnDataRx:  Existe una multitarea que revisa el stream de entrada del puerto en Android, en caso de encontrarse información, la almacena en un Array of Bytes de tipo TBytes o TArray<System.Byte>.
Ej.

procedure TForm4.BluePort1DataRx(Data: TArray<System.Byte>; len: Integer);
Var
   S: String;
   I: Integer;
Begin
   //Convierte el array en un string

   S := '';
   For I := 0 to len - 1 do
   begin
      S := S + Chr(Data[I]);
   end;

   //Actualiza los componentes visuales.

   tThread.Queue(Nil,
      procedure
      begin
         Try
            Memo1.Lines.BeginUpdate;
            Memo1.Lines.text := Memo1.Lines.text + S;
         Finally
            Memo1.Lines.EndUpdate
         End;
      end);

end;

OnDataTx:  El evento se ejecuta justo antes de enviar la información al puerto.

Ej.
procedure TForm4.BluePort1DataTx(Data: TArray<System.Byte>; len: Integer);
Var
   S: String;
   I: Integer;
begin
   S := '';
   For I := 0 to len - 1 do
   begin
      S := S + Chr(Data[I]);
   end;

   tThread.Queue(Nil,
      procedure
      begin
         Try
            Memo1.Lines.BeginUpdate;
            Memo1.Lines.text := Memo1.Lines.text + S;
         Finally
            Memo1.Lines.EndUpdate
         End;
      end);
end;


Métodos

Procedure Write(Value: String): Permite enviar al Puerto un string,  el componente convierte cada carácter en un byte, asignándolo a una posición de un byte en el buffer de envío, por lo tanto no es necesario declarar el string de ningún tipo particular.
Ej. Blueport1.Write(‘Este es el texto a envíar’);

Procedure Write(Value: Integer):   Permite enviar un entero o byte por el Puerto serial bluetooth.
Ej. BluePort1.Write(65);

 Procedure Write(Value: TBytes): Permite enviar un array de bytes al Puerto.
Ej. BluePort1.Write(TBytes.Create($A, $45, $FF, $10));

Procedure Write(Stream: TStream):  Permite enviar el contenido de un stream directamente al puerto
procedure TForm4.BtnEnviarClick(Sender: TObject);
Var
  St : TMemoryStream;
begin
   St := TMemoryStream.Create;
   St.LoadFromFile(FileName);
   St.Position := 0;
   BluePort1.Write(St);             
end;

Pueden descargar el componente desde

Espero sus comentarios y mejoras del componente.

Si realizan proyectos de este tipo, favor enviar videos del uso del Bluetooth o documentos.

Gracias.




lunes, 9 de diciembre de 2013

Exitoso Proceso de Certificacion en Embarcadero Developer


Hola a todos

Quiero compartir con ustedes el resultado de nuestro proceso de certificación en "Delphi Developer Certification", con los instructores del SENA (Servicio Nacional de Aprendizaje).

En este proceso intervinieron instructores del SENA quienes participaron activamente en seminarios presenciales en la Ciudad de Bogotá, Colombia y en seminarios virtuales, donde afianzaron sus conocimientos sobre la herramienta en los temas que hemos llamado los cimientos de la programación profesional.

En esta primera sesión de certificación participaron 20 instructores de los cuales todos superaron la prueba, demostrando además de los conocimientos básicos del lenguaje, las mejores prácticas de desarrollo de software profesional en RadStudio.

Esta certificación se logra gracias al convenio entre ITTools Distribuidor exclusivo para Colombia y el SENA, quienes definieron un proyecto para la innovación en sus programas de Técnicos y Tecnólogos en sistemas.

Quiero agradecer a todos los instructores por su esfuerzo y dedicación, en especial en estas últimas semanas del proceso, al SENA y a ITTools por su preocupación y apoyo logístico, técnico y profesional.

Felicitaciones a todos.
 




 

domingo, 24 de noviembre de 2013

Conexión a bases de datos vía Internet


Si tienes un programa de escritorio con conexión a bases de datos Sql, es probable que en algún momento pienses en conectarlo remotamente al servidor vía internet,  ya sea porque tienes sedes en lugares geográficos diferentes, porque viajas frecuentemente o muchos otros aspectos que pueden requerir de esta infraestructura.
Esta situación solo ocurre con los programas de escritorio, ya que las Aplicaciones WEB están diseñadas para trabajar remotamente y pueden ejecutarse desde el navegador en cualquier ubicación geográfica sin problemas.
Si desean conocer las ventajas o desventajas de las Aplicaciones Nativas Vs. las Aplicaciones Web pueden revisar la siguiente entrada en este foro.
 
Para conectarse a una base de datos remota vía Internet, hay 4 opciones válidas en este momento: 
1. Escritorio Remoto:- Utilizar software de escritorio remoto,  prácticamente con este software no es una conexión a base de datos remota vía Internet, pero la incluyo en el listado ya que finalmente cumple el mismo objetivo,  la gran ventaja de este modelo es la facilidad de uso, no hay que aprender nada nuevo ni realizar cambios en las aplicaciones, solo es instalar el servidor de escritorio y dar accesos a los clientes.
Desventajas hay muchas, partiendo del hecho de la seguridad ya que debes dar permisos a la sesión de Windows completamente y de allí partir a controlar los accesos a las aplicaciones, esto supone un desgaste en administración de usuarios.
Este modelo consume gran ancho de banda, porque debe transmitir el escritorio completo en forma de imágenes,  así que lo que no consume en el traslado de de datos si lo consume en el traslado de imágenes. 
 
2. Virtual Private Network:- La segunda opción es crear una VPN (Virtual Private Network) existen programas para eso y es relativamente fácil, la ventaja es que extiende la red local sobre internet, es segura y muy simple de manejar. la desventaja es que requiere instalar software de VPN en cada cliente y cuando se conecta la VPN  asigna una IP nueva al equipo dado que prácticamente cambia de red. 
Otra gran desventaja es que consume mucho ancho de banda, por lo cual es necesario tener buenos canales de comunicación, aún así no es recomendable tener muchos usuarios en este modelo, creo que más de 10 o 20 usuarios según el ancho de banda puede ser el límite en conexiones normales de internet. 

 
3. Conexión Directa a la Base de Datos:- El tercer modelo definitivamente no lo recomiendo y es solo abrir el puerto y el IP de tu base de datos a internet configurando el FireWall, primero por el aspecto de seguridad y segundo porque las bases de datos están diseñadas para trabajo en conexiones estables, así que si la conexión se cae, tu aplicación se cae inmediatamente. afectando no solo el cliente, sino también el rendimiento del servidor. ¿Que si se puede hacer?, si se puede, que lo debas hacer?, definitivamente no. 

4. Uso de un MiddleWare:- La cuarta opción y la más usada y recomendada en este momento para redes como internet, donde hay límites de la velocidad de acceso y la conexión no es estable (Se puede caer en cualquier momento), es el uso de un software mediador llamado genéricamente Middleware.
El middleware permite crear aplicaciones en 3 capas y 3 niveles, este software es quien maneja la seguridad, la conectividad y la persistencia de los datos. 
Hay varias tecnologías para hacer esto, en Delphi se conoce como DataSnap anteriormente Midas, en Microsoft se denomina tecnología Com, en Java se utiliza JBoss o Tomcat. Según la tecnología que se quiera usar se puede utilizar navegador o conexiones directas sin el navegador,
Entre las tecnologías de conexión directa encontramos el DataSnap de Embarcadero y la tecnología Com de Microsoft. Con navegadores WEB igualmente se puede implementar las 3 capas utilizando tecnologías WebServices o Rest. 
Si estás trabajando en Delphi te recomiendo la tecnología DataSnap para crear aplicaciones de este tipo, o utilizar el HyperBase server, el cual ya tiene las opciones implementadas y es solo cuestión de usarlo. 

jueves, 21 de noviembre de 2013

Paso de parámetros AnsiString y UnicodeString en DLLs

Una de mis formas preferidas de implementar funcionalidades nuevas en aplicaciones legadas (Versiones de Delphi < 2009) es el uso de los DLLs,  aunque en general prefiero no usar los Dlls para evitar problemas de versiones, es la única manera de hacerlo en algunos casos.
El paso de parámetros no es una tarea sencilla entre la aplicación y el DLL, de hecho es el mayor foco de errores después del manejo de las multitareas entre los desarrolladores principiantes en este tema.
Hoy hablaremos de la forma de enviar parámetros de tipo String entre la aplicación y una DLL, pero agregaremos un nivel mayor de complejidad al realizarlo también entre versiones diferentes de Delphi que ya han redefinido el String por una versión Multibyte  denominada UnicodeString.
Primero haremos una breve descripción de los tipos de String para que puedan entender la complejidad que esto representa aunque no ahondaremos en el tema, dado que no es el eje del presente artículo.
A partir de Delphi 2009 se realizó un cambio importante en el lenguaje, que a la fecha es la razón principal por la cual muchos desarrolladores no han migrado sus aplicaciones a las versiones recientes de Delphi.  La definición del String como variable pasó de ser un AnsiString a un UnicodeString.
En UnicodeString  cada carácter está representado por dos bytes, mientras que en un AnsiString cada carácter está representado por un byte. Veamos un ejemplo con el siguiente código

 Var
   St : String;
Begin
  St := 'Hola';

 
En Versiones anteriores a Delphi 2009 la representación en memoria de la variable St ocupa 5 bytes, uno por cada letra y un cero al final de la siguiente forma :

H
o
l
a
0

En las versiones recientes de Delphi >= 2009 físicamente ocupa 10 bytes y se representa así

$ 0
H
$ 0
o
$ 0
l
$ 0
a
$ 0
$ 0
Ch1
Ch2
Ch3
Ch4
Fin

  
No es un aspecto para preocuparse, dado que Delphi en versiones recientes maneja automáticamente las conversiones entre Ansi y unicode cuando se requiere, y en cualquiera de los casos la función Length(St) retornará el mismo valor que corresponden a la cantidad de caracteres y no a los bytes necesarios para su almacenamiento, es decir esto solo afecta el almacenamiento y las funciones que acceden al String en forma de bytes, pero no las funciones de alto nivel de tratamiento de Strings.

Así que mientras que se trabaje dentro del entorno Delphi no hay ningún inconveniente en el manejo y asignación de los diferentes tipos de String,  pero en el momento que se requiere hacer interfaces con otros programas como los Componentes COM de Microsoft o los DLL si es importante conocer las estructuras internas de las variables que se pasan como parámetros.
Para mayor información sobre los Unicode String pueden acceder al siguiente link del maestro Marco Cantú http://edn.embarcadero.com/article/38980

 

Paso de parámetros String a un DLL

Es fácil pasar parámetros simples desde una aplicación hacia una DLL, dado que su estructura es simple y pueden pasarse en los registros del procesador, es decir que "caben" en las variables del procesador y en ninguna forma tienen que apuntar a posiciones de memoria adicionales.
Sin embargo para el paso de parámetros más complejos que requieren posiciones de memoria adicionales, como el caso de los String que pueden medir varios miles de bytes, es necesario utilizar métodos adicionales de paso de dicha información.
La solución más simple es el uso de un administrador de memoria, el cual se encarga del manejo de esta situación, Delphi trae por defecto una librería denominada BORLNDMM.DLL y se incluye tanto dentro de la aplicación como del DLL una unidad denominada ShareMem, también existen otras librerías para el mismo fin en el mercado que pueden hacer mejor esta misma tarea. 
Debe tener en cuenta que si incluye la unidad ShareMem en los uses del DLL será obligatorio distribuir junto con la aplicación la librería BORLNDMM.DLL.
En caso de no utilizar estas librerías es necesario tener algunos cuidados, en primer lugar no es posible pasar el parámetros String, excepto los ShortString como parámetros, así que todos los Strings se pasarán como array de bytes o como PChar.

library Project23;

uses
  System.SysUtils,  System.Classes,  Vcl.Dialogs;

{$R *.res} 
   Procedure MuestreMensaje(Const Valor : PChar); stdcall;
   Begin
      ShowMessage(Valor);
   End; 

Exports
  MuestreMensaje; 

begin
end.


El llamado desde la aplicación puede realizarse de forma estática o dinámica, pero cualquiera que sea la forma de llamado de la función el paso de parámetros quedaría algo similar a esto:

Var
  Texto : String;
Begin
   Texto := 'Hola esta es una prueba de llamado a un DLL';
   MuestreMensaje(PChar(Texto));
 

Retorno de Strings en una función de un Dll

Este es un punto delicado en el paso de parámetros, dado que el área de memoria de la aplicación principal es independiente del área de memoria del DLL,  por lo que no necesariamente pueden compartir memoria o punteros de memoria entre los dos módulo, en especial si la carga del DLL se realizó dinámicamente utilizando la función LoadLibrary en lugar de la forma estática.
Debe tener en cuenta que la memoria de la aplicación estará disponible siempre que la aplicación esté activa, lo que no sucede con la memoria del módulo, esta se destruye al liberar la librería con FreeLibrary,  por lo tanto los swings que se creen en el módulo se perderán después del liberado, causando mal funcionamiento de la aplicación en forma aleatoria.
Por eso en el momento de retornar un String debe separarse una porción de memoria del entorno global del sistema y no del área de memoria del módulo, así que este modelo de retorno de un String está mal diseñado y puede causar errores aleatorios en la aplicación, en especial cuando se realiza una carga dinámica del módulo.

 

//  ESTE MODELO DE PROGRAMA ESTÁ MÁL DISEÑADO,
//    PRESENTA PROBLMAS DE ASIGNACIÓN DE MEMORIA

library Project24;
uses
  System.SysUtils,  System.Classes,  System.StrUtils;

{$R *.res} 

   Function InvierteString(Const Valor : PChar) : PChar; stdcall;
   Begin
      Result := PChar(ReverseString(Valor)); //Error por asignación de memoria
   End; 

Exports
  InvierteString;

begin
end.


Solución al Problema 

Se debe utilizar la función GetMem para reservar memoria suficiente para almacenar el String de retorno, copiar en esta porción de memoria el String y retornar un puntero a la posición de memoria, para esto ya he creado la siguiente función: 

const charlen = 1; //1 para AnsiString, 2 para UnicodeString 

Function StrToPChar(Res : String) : PChar;
 Var
    I : Integer;
 Begin
    I := Length(Res)*charlen;
    If I > 0 then
    Begin
       GetMem(Result,I+1);
       Result := StrPCopy(Result, Res);
    End
    Else
    Begin
       GetMem(Result,2*charlen); //string vacio debe tener al menos 2 o 4 bytes.
       Result := StrPCopy(Result,'');
    End;
 End; 

Así la función de retorno tendrá la siguiente forma

   Function InvierteString(Const Valor : PChar) : PChar; stdcall;
   Begin
      Result := StrToPChar(ReverseString(Valor));
   End; 

Mezclando versiones de Delphi

Este método funciona muy bien si tanto la aplicación como el módulo DLL están utilizando ambos el mismo modelo de String, es decir ambos utilizan AnsiString o UnicodeString, pero que sucede cuando se requiere llamar desde Delphi 7 un Dll creado en XE5?
El problema en este modelo es la representación de la variable String en cada una de las versiones, como hemos mencionado para las versiones 2009 y posteriores definir una variable String por defecto es Unicode mientras que para las versiones anteriores se representa como AnsiString.
En una versión anterior a la 2009 la constante charlen debe ser igual a 1, dado que cada carácter ocupa solamente un byte, de esta manera la función GetMem reserva la cantidad de memoria correcta del String, mientras que para versiones 2009 o posteriores será necesario asignar el charlen a 2.

Para el llamado de la función veremos dos casos, el primer caso sería llamar una DLL desarrollada en Delphi XE desde Delphi 7 y luego el caso contrario, hacer un llamado a una DLL desarrollada en XE desde Delphi 7. 

Llamar una DLL desarrollada en Delphi XE5 desde Delphi 7

Aunque el ejercicio se desarrollo en estas versiones lo que se quiere indicar es cómo hacer el llamado de una función creada en un Dll en una versión >= Delphi 2009 desde un programa desarrollado en Delphi < 2009.
Es importante hacer el cambio de tipo de String de Delphi 7 (AnsiString) a un formato de dos bytes por carácter y eso se logra de la siguiente forma:
 

Funcion  LLamaFuncionXE5(Nombre : String) : String;
Var
   P : PWideChar;
begin

   P := PWideChar(WideString(Nombre)); //Se convierte de AnsiString a WideChar
   Result := String(InvierteString(P)); //El typecast aquí es automático
 

Llamar una DLL desarrollada en Delphi 7 desde Delphi XE5

Aunque el ejercicio se desarrollo en estas versiones lo que se quiere indicar es cómo hacer el llamado de una función creada en un Dll en una versión anterior a Delphi 2009 desde un programa desarrollado en Delphi >= 2009.

 

Funcion  LLamaFuncionD7(Nombre : String) : String;
Var
   P : PAnsiChar;
begin 
   P := PAnsiChar(AnsiString(Nombre));//Se convierte de UnicodeString a AnsiChar
   Result := String(InvierteString(P)); //El typecast aquí es automático
 

Llamar una DLL desarrollada en Delphi con compatibilidad de String

Obviamente si tanto el Dll está desarrollado con la misma definición de Delphi String, no debe haber problemas, dado que existe la compatibilidad y no será necesario hacer typecast, la función quedaría de la siguiente forma

Funcion  LLamaFuncion(Nombre : String) : String;
begin
   Result := String(InvierteString(PChar(Nombre)));

 
Esta metodlogía puede ser muy útil para realizar funcionalidades que solo están disponibles en versiones recientes de Delphi especialmente en RadStudio que no es posible desarrollar en versiones anteriores a la 2009. 
Esta información es válida para DLL desarrollados en otros lenguajes como C y también para desarrollar funciones para ser ejecutadas desde otros lenguajes, dado que los DLL en Windows deben ser compatibles entre los diferentes lenguajes de programación.
 

Atte.Mg. Gustavo Enríquez
http://www.hyper-soft.co

miércoles, 20 de noviembre de 2013

Componentes de Bases de datos y Middleware HBX

Que es HyperBase Suite?

HyperBase Suite es un conjunto de herramientas que permiten implementar de forma fácil, rápida y segura la capa de lógica de negocio en las aplicaciones corporativas.  Es un middleware de Datos, Procedimientos, Reportes y Gráficos entre otros.

HyperBase está diseñado para trabajar sobre redes de públicas de internet, lo cual exige que implemente opciones de recuperación de conexiones de forma automática, redundancia de conectividad, escalabilidad y los más importante conexiones seguras, por eso HyperBase maneja todas sus conexiones bajo protocolos SSL además de encriptación con llave privada y compresión de datos.

Que son los componentes HBX?

Los componentes HBX representan las herramientas de conexión al HyperBase en sus dos versiones, en la versión Local y versión Server.  en otras palabras podría decirse que los componentes HBX permiten la conexión a las bases de datos, ejecutar consultas y procedimientos almacenados sobre las bases de datos y en el caso del server, permite ejecutar consultas y procedimientos almacenados sobre HyperBase.
Opcionalmente en el caso que estén disponibles los módulos de Report Server y Chart Server,  permiten la ejecución de de reportes y gráficos estadísticos en forma remota al servidor.

Para que plataformas están disponibles los Componentes HBX?

En modo Local solo están disponibles para Windows, desde las versiones XP en adelante, con conexión al servidor, existen versiones para Android, IOS (IPhone, IPad), MAC OSX y Windows.
Que lenguajes soportan los HBX?

Hay conectores nativos para PHP, Java, C++ y Delphi sobre Windows, también existen drivers ActiveX y ODBC en Windows que permite la conexión desde cualquier lenguaje que soporte este tipo de conexiones, por otro lado Android, IOS y OSX solo están soportados en lenguaje Delphi y C++ de Embarcadero RadStudio XE5.
A que bases de datos permiten conectarse los componentes HBX?

Permite las conexiones nativas a Oracle, MS SQL Server, IBM DB2, Postgres, Interbase, FireBird, MySQL, Informix, SyBase ASA, SyBase ASE y otros por medio de ODBC.

Que se requiere para la distribución de la aplicación final?

Los componentes HBX son Royalty Free, eso quiere decir que puede distribuir los componentes como parte de su aplicación sin ningún costo para el cliente final. Sin embargo es posible que sea necesario instalar software adicional cliente que es distribuido con el motor de la base de datos.  En el caso particular de Postgres Sql, será necesario adquirir el driver correspondiente.

Existen dos modos de utilizar los componentes HBX, el modo Local y el Modo Server, El modo local funciona como una aplicación Cliente/Servidor tradicional, mientras que en modo server se convierte en una aplicación N-Tiers (Multiples capas), eso permite construir o partir de aplicaciones Cliente/Servidor tradicionales y llevarlas a aplicaciones de 3-Capas de una forma fácil e intuitiva.

En modo Local los conectores HBX requieren la instalación en el equipo cliente de los drivers para su conexión y dependiendo del motor de base de datos, podría requerir la instalación del software del cliente de la base de datos en cada una de las máquinas donde se ejecutará el programa.

En modo Remoto y aplicaciones desarrolladas en lenguajes diferentes a Delphi XE5, se requiere de solamente un archivo .dll que realiza la conexión con el servidor HyperBase.

Para las aplicaciones desarrolladas en Delphi XE5 y posteriores,  la conexión con el servidor HyperBase no requiere ningún tipo de archivo adicional, así que bastará con el ejecutable para su ejecución de forma remota.



Descripción básica de uso en los diferentes lenguajes, PHP, Java, C y Delphi.
 

Realizaremos una breve descripción del funcionamiento de los componentes HBX en los diferentes lenguajes.
PHP. 

Ejercicio realizado en PHP, realiza una consulta a la tabla ciudades, guarda el contenido en formato XML en una carpeta local y luego muestra el resultado de la consulta en una página HTML.

<?php

   include '\http\www\sig\hbclasses.php';

   $HbConn = new THbConn("","","127.0.0.1",211,"Login","Password");

   $HbConn->DoConnect();
           $Query = $HbConn->NewQuery("DbScript","Select * from ciudades",-1);
           $Query->Open();
           $Query->SaveToFile("c:/temp/queryciudades.xml");

   //echo $Query->GetXmlData();
            $Query->First();

   while ($Query->Eof() != TRUE)
           {
               print($Query->FieldValue("NOM_CIUDAD"));
              print("<br>");
               $Query->Next();
           }

   $HbConn->DoDisconnect();

?>
 
JAVA.

 Ejercicio realizado en Java, realiza una consulta a la tabla ciudades y muestra el resultado de la consulta en una página HTML.

public static void main(String[] args) {
 
   JHBConnection con = new JHBConnection("","","127.0.0.1",211,"Login", "Password");
 
   con.DoConnect();
 
   JHBQuery query = con.NewQuery("DbScrip", "select * from ciudades ",-1);
 
   query.Open();
 
   int n = query.FieldCount();

   //System.out.println(n);

   for (int i = 0; i < n; i++) {
     System.out.println(query.GetField(i).getFFieldName()+"="+
             query.FieldByName("nom_ciudad").GetAsString());
   }

   query.Close();
 
   query.Destroy();
 
   new JNIFrame(){
 
     public void cerrando(JHBConnection con){
 
        System.out.println("cerrando2...");
 
        if(con!=null && con.GetConnected())
           con.Destroy();
      }

   };

}

}

 C Estandar. 

Ejercicio realizado en C Estandar, no se utiliza la Programación orientada a objetos ni ninguna de las características modernas del lenguajes.

void main(void)

{
   int DbHandle;
   int TableHandle;
   int I; 

   DbHandle = RHBNewConnection("", "");

   I = RHBOpenConnection(DbHandle, "127.0.0.1", 211,"Login","Password");
   TableHandle = RHBNewQuery(DbHandle, "DbScript", "Select * from Ciudades", -1); 

   RHBOpen(TableHandle);
   RHBSaveToFile(TableHandle, "c:\\temp\\queryciudades.xml");

   RHBFreeQuery(TableHandle);
   RHBCloseConnection(DbHandle); 

   printf("Fin\n");

}

 
Delphi consola.

Ejercicio realizado en Delphi compatible desde Delphi 7 hasta Delphi XE5, en sistemas operativos Windows, Mac, Android y el IOS.

Var
  DbConn : THBXConnection;
  Query: THbQuery;
begin
   DbConn := THBXConnection.Create(
                  'serverhost',  '211', 'login', 'password');

   DbConn.ConnectionType := TcxNative;
   DbConn.Open; 

   Query := DbConn.NewQuery('DbScript','select * from ciudades');
   Query.Open;
   Query.SaveToFile(FilePath+'queryciudades.xml', dfXMLUTF8); 

   While not Query.Eof do
   Begin
      //La siguiente instrucción cambia de acuerdo a la plataforma
      //Dado que Writeln es de tipo consola
      Writeln(Query.Fields[0].FieldName+' : '+Query.Fields[0].AsString);
      Query.Next;
   End; 

   Query.Close;
   DbConn.Close;
end;


Delphi Visual. 

Ejercicio realizado en Delphi compatible desde Delphi 7 hasta Delphi XE5, en sistemas operativos Windows, Mac.





El código fuente sería el siguiente:
 
 
 
 
Delphi IPhone, IPad o Android. 

Ejercicio realizado en Delphi XE5 para Android o IOS, en el uso de dispositivos móviles es indispensable el uso del servidor HyperBase, no es posible trabajar en modo Local.




 


Componentes HBX

Esta es una lista de los componentes HBX y los comparamos con los BDE que son los más conocidos de Delphi.


Componentes HBX
 





THbxConnection:

Este componente realiza la conexión a un servidor ya sea remoto o local, En el caso de conexión Local, es el mismo componente quien administra las diferentes conexiones a bases de datos a las cuales se tiene acceso.  si es conexión Remota, será el servidor HyperBase quien administrará las conexiones a las bases de datos.

 

 
 



THbQuery:

El componente de Query permite cumplir una gran variedad de funciones, desde simular una tabla en memoria, representar una tabla de la base de datos, un query con la propiedad "Request live" activada, un store procedure y los sqlUpdate query entre otras cosas..

 
 



THBClientCallBacks:

Este componentes solo está disponible a partir de la versión XE2 de Delphi y permite la comunicación de mensajes entre las aplicaciones cuando trabajan con el servidor HyperBase.  Implementa un Middleware de mensajes entre los clientes del HyperBase,  permitiendo el envío de mensajes o comandos P2P y Broadcast.

Nota: Solo funciona en modo Server y versiones Delphi XE2 en adelante
 

 
 



THBServerList:

Permite la implementación del LoadBalance desde el Cliente, Este componentes administra una lista de servidores de los cuales el HbxConnection seleccionará para conectarse si está habilitada la opción de LoadBalance, en caso de no estar disponible el servidor, lo marcará como no disponible e intentará con el siguiente en la lista

Nota: Solo funciona en modo Server Server y versiones Delphi XE2 en adelante
 

 
 


Configuración de conexiones en modo Local

Es posible utilizar las conexiones a bases de datos en tiempo de diseño dentro del Delphi,  sin embargo para la instalación de los clientes, se recomienda realizar la configuración dinámicamente dentro del programa.

Las conexiones locales están disponibles solo para Windows y desde Delphi 7 en adelante,  para MAC OSX, IOS o Android solo está disponible la versión de conexión remota al HyperBase Server.

La herramienta para la configuración de las conexiones en Windows es el DbAdmin, en ella es posible crear conexiones a las bases de datos comerciales más conocidas o por medio de ODBC a otras conexiones a Bases de Datos.
 


 


Es posible Administrar conexiones a bases de datos sobre la LAN, que serán utilizadas por las aplicaciones tanto en tiempo de ejecución utilizando el componente THBXConnection.

Las conexiones del HyperBase en modo Local LAN utilizan el mismo sistema de conexión del DBExpress, esto quiere decir que son conexiones nativas a las bases de datos, y tienen adicionalmente un conector ODBC que permite realizar conexiones por medio de esta tecnología. 

Nota: Es necesario iniciar el DbAdmin con permisos de Administrador, ya que requiere almacenar información en el Registry del systema.  Esta opción se realiza así para evitar configuraciones no autorizadas.

 

Cómo Configurar una base de datos en tiempo de ejecución
 

Para implementar la funcionalidad de conexión Local en HyperBase, se distribuye con los componentes HBX un Mini Servidor de HyperBase, que implementa las funcionalidades básicas del servidor y que permite mantener la compatibilidad en todas las versiones de Delphi.

 

 

En los siguientes diagramas se muestra la arquitectura de los dos modelos,  en el modelo Server las conexiones a las bases de datos y los permisos de acceso se le asignan directamente al servidor HBServer,  por medio de una herramienta llamada HB SqlTools,  en este modelo no es posible crear una conexión a una base de datos en tiempo de ejecución.  Solamente el administrador podrá crear nuevas conexiones y con los permisos adecuados sobre el servidor.



 

En el modelo local la conexión se realiza en forma directa y el servidor local se ejecuta en el mismo equipo con la aplicación y se distribuye como un archivo .dll con el nombre de HBLConn.dll, el cual debe ser distribuido junto con el ejecutable.

En este modelo Local, primero es necesario realizar la conexión con el servidor HBServer Local y luego se le indica la acción que se quiere realizar, en el caso que nos ocupa, adicionar una nueva conexión a la base de datos.
 

En este ejemplo primero es necesario conectarse al servidor local, para ello se instancia la propiedad DbConn.ConnectionType := TcxLocal, de esta manera el conector utilizará el modelo Cliente/Servidor local, como la conexión se realiza en el mismo computador, no se requiere especificar ningún otro parámetro como Host, Login o Password.

 

Una vez realizada la conexión con el servidor, se procede a adicionar un alias de la base de datos, junto con los parámetros de conexión,  esta función sobrescribe cualquier conexión previa, incluyendo las definidas en el DBAdmin.

 

Esta conexión solo estará disponible en tiempo de ejecución, si desea establecer una conexión en tiempo de diseño, debe realizarse por medio de la herramienta DBAdmin, descrita en la sección anterior.

 

Var
  DbConn: THBXConnection;
  Query: THbQuery;
  DbParams : TStringList;
 
begin
   DbParams := TStringList.Create;
   DbParams.Add('HBDriverType=DBX');
   DbParams.Add('RoleName=RoleName');
   DbParams.Add('User_Name=sysdba');
   DbParams.Add('Password=masterkey');
   DbParams.Add('ServerCharSet=');
   DbParams.Add('SQLDialect=3');
   DbParams.Add('ErrorResourceFile=');
   DbParams.Add('LocaleCode=0000');
   DbParams.Add('BlobSize=-1');
   DbParams.Add('CommitRetain=False');
   DbParams.Add('WaitOnLocks=True');
   DbParams.Add('IsolationLevel=ReadCommitted');
   DbParams.Add('Trim Char=False');
   DbParams.Add('GetDriverFunc=getSQLDriverINTERBASE');
   DbParams.Add('LibraryName=dbxint.dll');
   DbParams.Add('VendorLib=GDS32.DLL');
   DbParams.Add('Database=\MYDATABASE.GDB');
   DbParams.Add('DriverName=InterBase'); 

   DbConn := THBXConnection.Create(Self);
   DbConn.ConnectionType := TcxLocal;
   DbConn.AddDataBase('DbName', DbParams);
   DbConn.Open;

 
Una vez conectado al servidor HbServer Local, es posible realizar otras operaciones con la conexión como las siguientes:

·         Function TestDb(Params: TStrings; Var Msg: String): Boolean

Esta función permite probar la conexión antes de adicionarla, simplemente indica si fue posible conectarse a la Base de Datos y en caso de error, en la variable Msg retornará el mensaje correspondiente.

·         Function RemoveDataBase(DbName: String): Boolean

Elimina una base de datos de la configuración, se libera la conexión con la base de datos.

·         Function ClearDataBases: Boolean

Elimina todas las conexiones a las bases de datos definidas en el HBServer local, el sistema retornará a las conexiones definidas en el DbAdmin por defecto.
Otras funciones del HBXConnection

Procedure GetDataBaseNames(DbNames: TStrings);
 
Procedure GetTableNames(DbName: String; List: TStrings;
              SystemTables: Boolean = False);
 
Procedure GetFieldNames(DbName: String; TableName: String; List: TStrings);
 
Procedure GetIndexNames(DbName: String; TableName: String; List: TStrings);
 
Procedure GetProcedureNames(DbName: String; List: TStrings);
 
Procedure GetProcedureParams(DbName, ProcedureName,
              PackageName: String; List: TStrings); 
 
Function  NewQuery(DbName, Sql: String): THbQuery;
 
Function  TestDb(Params: TStrings; Var Msg: String): Boolean;
 
Function  AddDataBase(DbName: String; Params: TStrings): Boolean;
 
Function  RemoveDataBase(DbName: String): Boolean;
 
Function  ClearDataBases: Boolean;
 
Function  BeginTransaction(DbName: String): String;
 
Procedure CommitTransaction(IdTransaction: String);
 
Procedure RollBackTransaction(IdTransaction: String);

 



DESCRIPCIÓN DE FUNCIONALIDADES THBXCONNECTION

Manejo de Transacciones (Modo Local)

El manejo de transacciones se ha implementado en el modo Local para mantener compatibilidad, sin embargo en modo servidor no es posible implementar este mismo modelo ya que el modelo es desconectado y por lo tanto una operación puede iniciarse en un servidor y terminar en otro completamente diferente.
 

 

Existe en el HBServer métodos para implementar las transacciones, sin embargo es un modelo diferente y por lo tanto será necesario modificar el código en los puntos donde se utilicen transacciones.

Las transacciones se definen por cada conexión a base de datos, aunque los identificadores de cada transacción llevan un consecutivo único sin importar la base de datos. Veamos un ejemplo:

Var
   DbConn: THBXConnection;
   Query: THbQuery;
   IdTran: String;
 
begin

   DbConn := THBXConnection.Create(Self);
   IdTran := DbConn.BeginTransaction('DBName');
   Query := DbConn.NewQuery('DBName', ''); 

   Try
      Query.Sql.Text := 'insert into Paises values(:CodPais, :NomPais)';
      Query.Params.ParamByName('CodPais').AsString := '100';
      Query.Params.ParamByName('NomPais').AsString := 'Colombia';
      Query.Execute; 

      Query.Sql.Text := 'insert into Ciudades '+
                       'values(:CodCiudad, :NomCiudad, :CodPais)';
 
      Query.Params.ParamByName('CodCiudad').AsString := '0101';
      Query.Params.ParamByName('NomCiudad').AsString := 'Armenia';
      Query.Params.ParamByName('CodPais').AsString := '100';

      Query.Execute; 

      DbConn.CommitTransaction(IdTran);
   Except
      On E: Exception do
      Begin
         DbConn.CommitTransaction(IdTran);
         ShowMessage('No se guardaron los cambios, '+
                    'se presentó el siguiente error'+#$D#$A+ E.Message);
      End;
   End;


El modelo de transacciones funciona igualmente en el entorno visual,  se inicia la transacción almacenando el Id de la transacción en una variable global,  se pueden realizar todas las operaciones sobre las tablas y finalmente se realiza el Commit o el RollBack.

Si se inicia una transacción, todas las operaciones sobre esa base de datos están sujetas a la terminación con el Commit o el RollBack, de no hacerlo se pueden presentar problemas de consistencia en los datos presentados al usuario posteriormente y es posible que la base de datos aplique automáticamente el RollBack, perdiendo los datos definitivamente.

 Qué pasa con el ApplyUpdates y las transacciones?

Las modificaciones que se realizan en ambiente visual o directamente sobre el resultado de un Select se procesan en memoria, por lo tanto se almacenarán en la base de datos definitivamente al aplicar la función ApplyUpdates.

El ApplyUpdates, crea una transacción en la base de datos y almacena físicamente todos los cambios realizadas desde la última vez que se ejecutó el ApplyUpdates.  pero aún así, si se ha iniciado una transacción, se puede deshacer ejecutando la función RollBack.  En otras palabras tiene prioridad la transacción sobre el ApplyUpdates.

Debe tener en cuenta que el ApplyUpdates se aplica sobre una sola tabla, mientras que las transacciones cubren todas las modificaciones realizadas a diferentes tablas de la misma base de datos.

Observación:  Reiteramos que el manejo de transacciones debe limitarse lo más posible, debido a que en el modelo desconectado no es posible implementar las transacciones de la misma manera y será necesario crear procedimientos en el servidor HyperBase para implementar esta funcionalidad.

Multitareas  (THRead Safe)

Los componentes HBX no son Thread Safe, por lo tanto hay que tener cierto cuidado en el manejo de las multitareas cuando se conectan a las bases de datos.  La mejor forma de trabajar en crear una Conexión por cada multitarea y liberarla al finalizar la multitarea.  Veamos un ejemplo.

Types
 
   TMyThread = class(TThread)
   private
      DbConn: THBXConnection;
      Query: THbQuery;
   protected
      procedure Execute; override;
   public
      constructor Create;
   end;

implementation 

constructor TMyThread.Create();
begin
   inherited Create(True);
   FreeOnTerminate := True;
   DbConn := THbxConnection.Create(Nil);
   DbConn.ConnectionType := tcxLocal;
   Query := DbConn.NewQuery('MyDbName','select * from dosomething');
   Suspended := False;
end;

 

procedure TMyThread.Execute;
begin
   DbConn.Open;
   Query.Open;

   Try
      Repeat
         //Aquí hace lo necesario con la base de datos
           ....
         //Aquí hace lo necesario con la base de datos
      Until Condición;
   Except
      On E: Exception do
      Begin
         //Aquí toma medidas con respecto a los errores
      End;
   End;
end;
 

Cada tarea debe configurar completamente la conexión a base de datos en caso que sea dinámica, el THBXConnection puede leer las configuraciones creadas por el DbAdmin.
 
DESCRIPCIÓN DE FUNCIONALIDADES THBQUERY

Conectar a una base de datos

Antes de realizar cualquier operación sobre la base de datos, es indispensable conectar el componente con la base de datos, para ello se utilizan las siguientes propiedades.

Var
  Query : THbQuery;
begin
   Query := THbQuery.Create(Nil);
   Query.RemoteServer := DbConn;
   Query.ProviderName := 'dsProvider';
   Query.DatabaseName := 'MyDbName';
   Query.Sql.Text := 'Select * from ciudades';
   Query.OnReconcileError := DoReconcileError;
   Query.Open;
End;

·         RemoteServer: Debe apuntar al componente THBXConnection.

·         ProviderName: Es una constante, siempre debe ser 'DsProvider';

·         DatabaseName: Indica sobre cual base de datos se realizará la consulta.

·         Sql: El sql que corresponde a la consulta.

El componente no reporta algunos errores inmediatamente, dado que son validados por el servidor tanto en el caso Local como el Remoto, así que esos errores disparan un evento llamado  DoReconcileError, Delphi proporciona rutinas especificas para el manejo de este tipo de errores, siempre es aconsejable crear un evento para controlar estos errores al crear un Query.

Types

TyMyClasss = Class...
  private
    procedure DoReconcileError(DataSet: TCustomClientDataSet;
             E: EReconcileError; UpdateKind: TUpdateKind;
             var Action: TReconcileAction);
end;
 
implementation 

procedure TFDataModule.DoReconcileError(DataSet: TCustomClientDataSet;
             E: EReconcileError; UpdateKind: TUpdateKind;
             var Action: TReconcileAction);
begin
   Raise Exception.Create(E.Message);
end;
Ejecución de Procedimientos remotos

Los componentes de HBX permiten ejecutar tanto los Procedimientos Almacenados (Store Procedures) de la base de datos, como los Meta Procedimientos de HyperBase, en esta sección solo hablaremos de los Store Procedures de la base de datos, en el manual del Server se presentará la forma de crear Meta Procedures, la asignación de permisos de ejecución y la manera de llamarlos desde el HbQuery.

Consideremos este ejemplo de un procedimiento en Interbase, el cual tiene como único parámetro de entrada el valor entero de la edad y retornará una lista de los empleados que son mayores de dicha edad:

  create procedure get_name (employee_age integer)
  returns (employee_name char(30))
  as
  begin
    for select name
        from employee
        where age > :employee_age
        into :employee_name
    do
        suspend;
  end
 

El Sql de ejecución del procedimiento sería así:
 

   Query.Sql.Text := 'Select * from Get_Name(27)';
   Query.Open;

 

Paso de Parámetros en un Store Procedure:
 

El paso de parámetros es igual que en cualquier consulta, se le antepone dos puntos (:) a la variable,  es importante definir el tipo, en especial cuando son fecha, ya que pueden presentar inconsistencia por formatos:

 

   Query.Sql.Text := 'Select * from Get_Name(:Edad)';
   Query.Params.ParamByName('Edad').AsInterger := 27;
   Query.Open;

 Guardar cambios localmente sin aplicar

Dado que los componentes trabajan desconectados, es posible que la conexión al servidor se pierda en cualquier momento, pero esta situación no debe ser un problema, ya que si se recupera la conexión el sistema continuará normalmente, pero en el caso de no poder recuperarse, es posible guardar los cambios localmente y posteriormente recuperar los cambios y aplicarlos.  veamos el procedimiento:

   Query.Sql.Text := 'Select * from ciudades';
   Query.Open;

   Query.Append;
   Query.FieldByName('Cod_Ciudad').AsString := '2020';
   Query.FieldByName('Nom_Ciudad').AsString := 'Bucaramanga';
   Query.Post;
  

   Try
      Query.ApplyUpdates(0);
   Except
      Query.SaveToFile('\temp\ciudades.xml', dfXMLUTF8);
   End;
End;

 

Procedure RecuperaModificados
Begin
   Query.Sql.Text := 'Select * from ciudades';
   Query.Open;
   Query.LoadFromFile('\temp\ciudades.xml', dfXMLUTF8);
   Query.ApplyUpdates(0);
End;

 
Leer los datos del Query por bloques

La Suite de HyperBase está diseñada para trabajar sobre redes lentas como las de internet, por lo tanto puede llegar a ser un grave problema intentar traer el resultado de un Sql que retorne un millón de registros.  Así que existen dos el Server tiene una variable que limita el número máximo de registros que un usuario puede solicitar, después del cual, el servidor da por terminada la consulta.

En segunda instancia el cliente puede solicitar que solamente traiga un pequeño bloque de esos registros, por ejemplo, solamente los 100 primeros registros:

   Query.Sql.Text := 'Select * from ciudades';
   Query.PacketRecord := 100;
   Query.Open;

 

Para traer los siguientes 100 registros es posible ejecutar el comando GetNextPacket, en caso de querer cambiar el número de registros a traer, es posible cambiar la variable PacketRecord.
   Query.PacketRecord := 500;
   Query.GetNextPacket; 

Si se hace un recorrido por la tabla, esta traerá automáticamente cada paquete en la medida que lo necesite, no es necesario realizar ningún llamado a la función del GetNextPacket. El siguiente ejemplo recorrerá toda la tabla hasta llegar al final, recuperando bloques de 100 registros cada vez que llega al final del bloque.
 

   While not Query.Eof do
   Begin
      //Aquí se realizan las operaciones
      .....
      Query.Next;
   End;  

   Try
      Query.ApplyUpdates(0);
   Except
      Query.SaveToFile('\temp\ciudades.xml', dfXMLUTF8);
   End;
End; 

Nota: La Propiedad RecordCount se refiere al número de registros que están cargados en la tabla, no al total de registros de esa tabla en la base de datos.


THBQuery como Memory Table

El THBQuery también puede comportarse como una tabla de memoria, sin ninguna conexión a las bases de datos,  Es posible crear la estructura de la tabla manualmente o leer un archivo xml guardado previamente de una consulta de la base de datos. 

Para crear el dataset de forma manual, hay que agregar los campos y los indices, utilizando las propiedades de FieldDefs e IndexDef,  es importante si se desea posteriormente guardar estos datos en alguna tabla identificar bien los tipos de datos, ya que si no coinciden, deberá realizar la actualización de la tabla manualmente:

Var
  Query : THbQuery;
 
begin
   Query := THbQuery.Create(Nil);
   Query.FieldDefs.Add('Codigo', ftString, 20, True);
   Query.FieldDefs.Add('Nombre', ftString, 40, True);
   Query.FieldDefs.Add('Fecha', ftDateTime, 0);
   Query.IndexDefs.Add('Idx', 'Codigo', [ixPrimary]);
   Query.CreateDataSet;
 

Note que no se asignó ningún valor a la propiedad RemoteServer ni al ProviderName,  de hacerlo, el DataSet intentará buscar la tabla en el servidor generando un error al no encontrar el sql correspondiente.

Es posible leer en un DataSet los datos previamente guardados por una consulta, como lo hemos visto en los ejemplos durante este documento,  si se requiere hacer esto, no se requiere asignar las propiedades de conexión al servidor.
Var
  Query : THbQuery;
 
begin
   Query := THbQuery.Create(Nil);
   Query.LoadFromFile('c:\temp\consulta.xml');
   ....
 
Nota: El llamado a la función LoadFromFile o LoadFromStream borra automáticamente cualquier configuración manual que se haya realizado a la estructura del DataSet.
 
THBQuery "Live Query" con múltiples tablas

Con algunos componentes de conexión a bases de datos solo es posible editar consultas que hacen referencia a una tabla en particular, usualmente las consultas que involucran múltiples tablas son de lectura solamente.

Con los HBX es posible crear consultas de múltiples tablas y asignar una como la tabla primaria, de tal suerte que se pueden modificar los campos de dicha tabla,  los campos restantes no estarán incluidos en las modificaciones.

Dos conceptos se deben tener en cuenta para lograr este objetivo, en primer lugar todos los campos correspondientes a la llave primaria de la tabla principal deben estar involucrados en la consulta y en segundo lugar, se debe asignar la propiedad TABLENAME antes de abrir la consulta, para que el servidor pueda asignar la tabla modificable por defecto. 

Var
  DBConn: THBXConnection;
  Query: THbQuery;

begin
 
   DBConn := THBXConnection.Create(Self);
   DbConn.Open;
   Query := DbConn.NewQuery('MyDbName','');
   Query.OnReconcileError := DoReconcileError;
 

   Query.Sql.Add('SELECT *');
   Query.Sql.Add('FROM CIUDADES');
   Query.Sql.Add(' INNER JOIN PAISES ON (CIUDADES.ID_PAIS=PAISES.ID_PAIS)'); 

   Query.TableName := 'CIUDADES';
   Query.Open; 

   If Query.Locate('IDCIUDAD','76001',[]) then
   Begin
      Query.Edit;
      Query.FieldByName('NOMBRE_CIUDAD').AsString := 'SANTIAGO DE CALI';
      Query.Post;

      Query.ApplyUpdates(0);
 End; 

Nota: Si no se asigna el evento DoReconcileError, el sistema no captura los errores correspondientes si llegasen a ocurrir.