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ámetro | Significado |
CONNECT TIMEOUT | Tiempo de espera para la conexión |
MAX QUERY TIME | Tiempo de espera para abrir una consulta |
TIMEOUT | Tiempo 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.
|