2011-12-30 25 views
5

Ayer me encontré con este problema cuando estaba ocupado escribiendo algunas pruebas de unidad con SQLLite. Mi entorno es Windows7/Delphi XE.El uso de un parámetro de fecha y hora con ADO (ODBC) pierde parte de tiempo

El uso de TADOQuery junto con un parámetro TDateTime da como resultado la pérdida de la parte de tiempo.

unit Unit1; 

interface 

uses 
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
    Dialogs, ADODb, DateUtils, DB; 

type 
    TForm1 = class(TForm) 
    procedure FormCreate(Sender: TObject); 
    private 
    { Private declarations } 
    public 
    { Public declarations } 
    end; 

var 
    Form1: TForm1; 

implementation 

{$R *.dfm} 

procedure TForm1.FormCreate(Sender: TObject); 

var DbConn : TADOConnection; 
    Qry : TADOQuery; 
    DT  : TDateTime; 

begin 
DBConn := TADOConnection.Create(nil); 
DBConn.ConnectionString := 'Provider=MSDASQL.1;Extended Properties="DRIVER=SQLite3 ODBC Driver;Database=:memory:;LongNames=0;Timeout=1000;NoTXN=0;SyncPragma=NORMAL;StepAPI=0;"'; 
// DBConn.ConnectionString := 'Provider=MSDASQL.1;Persist Security Info=True;User ID=%0:s;Password=%1:s;Extended Properties="DRIVER={MySQL ODBC 5.1 Driver};SERVER=localhost;PORT=3306;DATABASE=test;USER=root;PASSWORD=rrr;OPTION=1048579"'; 
Qry := TADOQuery.Create(nil); 
Qry.Connection := DbConn; 
try 
    DBConn.Connected := True; 
    Qry.SQL.Text := 'CREATE TABLE test(d datetime)'; 
    Qry.ExecSQL; 
    Qry.ParamCheck := True; 
    Qry.SQL.Text := 'INSERT INTO test (d) VALUES (:d)'; 
    //Qry.Parameters.ParseSQL(Qry.SQL.Text, True); // not needed 
    TryEncodeDateTime(1999, 12, 12, 10, 59, 12, 0, DT); 
    Qry.Parameters.ParamByName('d').Value := DT; 
    Qry.Parameters.ParamByName('d').DataType := ftDateTime; 
    Qry.ExecSQL; 
    Qry.SQL.Text := 'SELECT d FROM test'; 
    Qry.Open; 
    ShowMessage(FormatDateTime('MM/DD/YYYY HH:NN:SS', Qry.FieldByName('d').AsDateTime)); 
finally 
    FreeAndNil(Qry); 
    FreeAndNil(DbConn); 
end; 
end; 

Lo curioso es que, cuando comento la línea Qry.Parameters.ParseSQL(Qry.SQL.Text, True); Se trabajará muy bien. Necesito la parte ParseSQL porque estoy construyendo un mini-ORM, así que necesita saber qué parámetros deben mapearse. Algunas observaciones:

  • haciendo la misma prueba con MySQL5 muestra el mismo problema (independientemente de la parte ParseSQL).
  • Este código funciona con SQL Server y el controlador OLEDB.

He buscado en la red y que se encuentran algunos enlaces interesantes:

http://tracker.firebirdsql.org/browse/ODBC-27

http://embarcadero.newsgroups.archived.at/public.delphi.database.ado/201107/1107112007.html

http://bugs.mysql.com/bug.php?id=15681

El primer enlace sugiere 'fijar' ADODB.pas, algo que hago No quiero hacer Al leer el último enlace, parece que ADO mapea el valor de fecha y hora hasta la fecha.

respuesta No quiero oír: utilizar otra biblioteca/componente (como dbexpress, ZeosLib, ...)

No estoy seguro de lo que sería ser el enfoque más sensato para resolver este problema.

Como Linas y Marjan Venema sugirieron que puedo omitir la parte de ParseSQL. Entonces, el código funciona ahora con SQLlite SI omito la línea Qry.Parameters.ParamByName('d').DataType := ftDateTime;.

Pero MySQL se niega a guardar la parte de tiempo. ¿Estoy viendo un problema de compatibilidad entre ADO y MySQL ODBC aquí?

+0

Establecería el tipo de datos antes de establecer el valor, pero no estoy seguro de que eso suponga alguna diferencia. ¿Por qué la construcción de un mini ORM necesita ParseSQL? He construido algunas librerías de tipo ORM y nunca las necesité. IIRC Qry.SQL.Prepare también debe llenar la colección de parámetros. –

+0

Necesito saber qué parámetros tengo en mi consulta y los valores serán mapeados desde un objeto usando rtti. te refieres a Qry.Prepared: = true; ? No hace diferencia. – whosrdaddy

+1

@whosrdaddy No hay necesidad de llamar a ParseSQL por su cuenta. Se llama automáticamente cuando SQL.Text cambia (ParamCheck debe ser True). – Linas

Respuesta

8

He probado esto un poco usando SQL Server y tengo exactamente el mismo problema cuando uso MSDASQL.1 (ODBC). Su código funciona bien con SQLOLEDB.1 y SQLNCLI10.1.

Si especifica el tipo de parámetro para ser ftString, se guardará con el tiempo utilizando ODBC, (al menos en SQL Server).

Qry.Parameters.ParamByName('d').DataType := ftString; 
Qry.Parameters.ParamByName('d').Value := DateTimeToStr(DT); 

Nota: Tenga cuidado de ajustes locales cuando se utiliza DateTimeToStr puede que no consiga lo que su base de datos quiere que sea. Una apuesta segura sería usar yyyy-mm-dd hh:mm:ss[.fff].

Actualización:

También puede establecer el tipo de datos del parámetro preámbulos a adDBTimeStamp mismo. ADODB lo establece en adDate cuando usa ftDateTime.

Qry.Parameters.ParamByName('d').ParameterObject.Type_ := adDBTimeStamp; 
Qry.Parameters.ParamByName('d').Value := DT; 
+0

Convirtiendo en cadena solo bloquea la aplicación. AdDBTimeStamp funciona bien. Pero ahora mi código de mapeo está roto, y estoy obteniendo EOleException: "La aplicación usa un valor del tipo incorrecto para la operación actual". Pero sospecho que este es un problema con las variantes. ¡Gracias por tu respuesta! – whosrdaddy

0

tuve el mismo problema con vfpoledb (el controlador de Visual FoxPro), pero el truco con adDBTimeStamp no funcionaba. FWIW, eso es VFPOLEDB 9.0.0.5815 con Delphi 7 y también con RAD Studio 10 (Seattle).

En el caso de VFPOLEDB hay otra solución posible que se basa en el hecho de que en Fox la representación subyacente para valores de fecha/hora (tipo 'T') es la misma que la de los valores de fecha (tipo 'D ') aprobado por OLEDB, es decir, un doble de 64 bits donde la parte entera representa días desde el 1 de enero en el año 0001 y la parte fraccionaria representa la hora del día.

Si el campo de tabla tiene el tipo 'T' y OLEDB pasa una fecha, Fox coacciona el valor haciendo nada. Entonces todo lo que se necesita aquí es volver a agregar en la parte fraccionaria.

ejemplo, con start_time ser un campo de fecha/hora:

cmd.CommandText := 'insert into foo (start_time) values (:start_time)'; 
cmd.Parameters.ParamByName('start_time') := Now; 
cmd.Execute; 

Esto da 2016-05-22 00:00:00 en la tabla.

Ahora, agregue la parte fraccionaria de esta manera:

cmd.CommandText := 'insert into foo (start_time) values (:start_time + :fraction)'; 
t := Now; 
cmd.Parameters.ParamByName('start_time') := t; 
cmd.Parameters.ParamByName('fraction') := Frac(t); 
cmd.Execute; 

Esto da 2016-05-22 02:17:42. Incluso los segundos fraccionarios se conservan, aunque no se muestran.

Cuestiones relacionadas