Página principal
Artículos y trucos
Catálogo de productos
Ejemplos y descargas
Mis libros
Cursos de formación
Investigación y desarrollo
Libros recomendados
Mis páginas favoritas
Acerca del autor
 
En colaboración con Amazon
 
Intuitive Sight

El tiempo de espera en ADO

¿Le ha tocado luchar alguna vez con el maldito mensaje "Tiempo de espera agotado" al intentar el acceso a una base de datos de SQL Server? En la época en que el BDE dominaba la faz de la Tierra, el problema se solucionaba retocando algunos parámetros en la configuración del alias o del controlador SQL Link. Los parámetros en cuestión eran (por si lo ha olvidado):

ParámetroSignificado
CONNECT TIMEOUTTiempo de espera para la conexión
MAX QUERY TIMETiempo de espera para abrir una consulta
TIMEOUTTiempo de espera para las restantes operaciones

El segundo parámetro podía configurarse tanto en el controlador como en un alias específico; los restantes, sólo en el controlador.

Pero al entrar ADO en escena, los viejos trucos ya no valen para mucho. ¿Cómo se puede indicar un tiempo de espera en ADO? La explicación que sigue no está sacada de ninguna documentación "oficial", sino que es fruto del respetable método conocido como "prueba y error". Además, los experimentos los he realizado exclusivamente con SQL Server 7. Espero que me disculpéis si encontráis alguna inexactitud.

El primer sitio donde se me ocurrió mirar fue en la configuración de una cadena de conexión. E inmediatamente encontré un par de parámetros, Connect Timeout y General Timeout. "¡Bien!" - me dije - "asunto concluido".


Me aguardaba una sorpresa, desgraciadamente. Por más que me empeñase en dar un valor explícito en estos parámetros, Delphi tozudamente insistía en ignorarlos. De hecho, como podrá comprobar, la propiedad ConnectionString se modifica cada vez que se activa la conexión, y cualquier asignación que hayamos realizado sobre estos valores desaparece por arte de magia.

Sospeché entonces que si Delphi jugaba con ConnectionString era porque prefería que indicásemos los tiempos de espera por medio de propiedades. Efectivamente, la clase TADOConnection define las propiedades ConnectTimeout y CommandTimeout. Probé a modificar estos valores en la conexión y ataqué con saña a una inmensa tabla que me estaba dando dolores de cabeza. ¡Arghh, aquello seguía sin funcionar!

Para abreviar, finalmente descubrí que la propiedad CommandTimeout de la conexión existe por la sencilla razón de que podemos ejecutar comandos SQL a través de este componente. El método necesario es:

function Execute(const CommandText: WideString;
   ExecuteOptions: TExecuteOptions = []): _RecordSet; overload;
procedure Execute(const CommandText: WideString;
   var RecordsAffected: Integer;
   ExecuteOptions: TExecuteOptions = [eoExecuteNoRecords]); overload;

Era necesario indicar de alguna forma el tiempo de espera de esta operación, y Borland decidió añadir esta propiedad al componente. Aclaro de paso que otras propiedades de TADOConnection tienen el mismo propósito, y que algunos de sus métodos que devuelven información de catálogo se implementan también como comandos.

Como podrá adivinar, el componente TADOCommand ofrece un CommandTimeout. Esto está muy bien para ejecutar operaciones de actualización y del lenguaje de definición de datos, pero tampoco nos resuelve el problema para una consulta. Por lo tanto, arrojé un TADOQuery en la superficie de un formulario ... ¡y vi que no existía una propiedad que controlase el tiempo de espera!

Sin embargo, la propiedad existe en TADODataSet, e indagando un poco más encontré que se introducía en la sección protected de la clase base TCustomADODataSet. La clase TADODataSet la hace pública, pero por algún oscuro designio Borland decidió que TADOQuery, TADOTable y TADOStoredProc eran merecedores de dicho privilegio.

Está claro que una solución posible es prescindir de las consultas y utilizar siempre TADODataSet. No es una idea descabellada porque, a diferencia de lo que sucede con los componentes del BDE, las implementaciones de las consultas, tablas y procedimientos almacenados de ADO están basadas en un mismo sistema de acceso, definido en la clase TCustomADODataSet, y se limitan a simplificar la especificación del conjunto de datos con el que deseamos trabajar.

¿Por qué entonces no utilizar TADODataSet y olvidarnos del asunto? Porque este componente exige que tecleemos la instrucción SQL en su propiedad CommandText, y alguno de los genios de Borland decidió dotar a esta propiedad del editor más incómodo, feo e inútil que he tenido la desdicha de encontrar. Si es usted una persona de nervios templados, pruebe su funcionamiento.

La solución final a la que recurrí consiste en un sucio truco para acceder a la propiedad escondida en un TADOQuery. He aquí el código que ejecuto durante la inicialización de un módulo de datos:

type
  TFriendDS = class(TCustomADODataSet);

procedure TDataModule1.Loaded;
var
  I: Integer;
begin
  inherited Loaded;
  for I := 0 to ComponentCount - 1 do
    if Components[I] is TADOQuery then
      TFriendDS(Components[I]).CommandTimeout := 120; // Dos minutos
end;

La clave está en que el acceso la propiedad CommandTimeout se implementa del mismo modo en la clase base y en la clase derivada. Por lo tanto, si definimos una nueva clase derivada a partir de la original, se seguirá cumpliendo esta afirmación, y la bestial conversión de tipo que hacemos es, sin embargo, correcta y segura. Además, Object Pascal establece que dentro de la unidad donde se define una clase, cualquier objeto o procedimiento tiene acceso irrestricto a sus datos privados o protegidos; una implementación rudimentaria de la cláusula friend de C++. Al definir TFriendDataSet en el módulo de datos, obtenemos el derecho a manipular los recursos protegidos de la nueva clase, y esto incluye a los recursos heredados desde TCustomADODataSet. El círculo se ha cerrado.