2011-12-30 9 views
5

Mi sono imbattuto in questo problema ieri quando ero impegnato a scrivere alcuni test di unità usando SQLLite. Il mio ambiente è Windows 7/Delphi XE.L'utilizzo di un parametro datetime con ADO (ODBC) perde la parte del tempo

L'utilizzo di TADOQuery in combinazione con un parametro TDateTime comporta la perdita della parte relativa all'ora.

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; 

La cosa divertente è che quando io commento la linea Qry.Parameters.ParseSQL(Qry.SQL.Text, True); apparecchio funzionerà regolarmente. Ho bisogno della parte ParseSQL perché sto costruendo un mini-ORM quindi è necessario sapere quali parametri devono essere mappati. Alcune osservazioni:

  • fare lo stesso test con MySQL5 presenta lo stesso problema (a prescindere dalla parte ParseSQL).
  • Questo codice funziona con SQL Server e il driver OLEDB.

Ho cercato la rete e trovate alcuni link interessanti:

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

Il primo collegamento suggerisce 'fissare' ADODB.pas, una cosa che faccio non voglio fare. Leggendo l'ultimo collegamento, sembra che ADO esegua il mapping del valore datetime aggiornato.

risposta io non voglio sentire: utilizzare un'altra libreria/componente (come dbExpress, Zeoslib, ...)

Non sono sicuro di quello che sarebbe essere l'approccio più ragionevole per risolvere questo problema.

Come suggerito da Linas e Marjan Venema, posso omettere la parte ParseSQL. Quindi il codice funziona ora con SQLlite SE ometto la riga Qry.Parameters.ParamByName('d').DataType := ftDateTime;.

Ma MySQL si rifiuta di salvare la parte del tempo. Qui vedo un problema di compatibilità tra ADO e MySQL ODBC?

+0

Avrei impostato il tipo di dati _prima_ impostando il valore, ma non sono sicuro che avrebbe fatto alcuna differenza. Perché la costruzione di un mini ORM richiede ParseSQL? Ho creato alcune librerie di tipo ORM e non ne ho mai avuto bisogno? IIRC Qry.SQL.Prepare dovrebbe anche popolare la raccolta Parameters. –

+0

Ho bisogno di sapere quali parametri ho nella mia query e i valori saranno mappati da un oggetto usando rtti. vuoi dire Qry.Prepared: = true; ? non fa differenza. – whosrdaddy

+1

@whosrdaddy Non è necessario chiamare ParseSQL da solo. Si chiama automaticamente quando SQL.Text cambia (ParamCheck deve essere True). – Linas

risposta

8

Ho provato questo un po 'usando SQL Server e ho lo stesso identico problema quando uso MSDASQL.1 (ODBC). Il tuo codice funziona bene con SQLOLEDB.1 e SQLNCLI10.1.

Se si specifica il tipo di parametro su ftString, verrà salvato con il tempo utilizzando ODBC (almeno su SQL Server).

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

Nota: Fare attenzione di impostazioni locali quando si utilizza DateTimeToStr potrebbe non produrre ciò che il vostro db vuole che sia. Una scommessa sicura sarebbe quella di utilizzare yyyy-mm-dd hh:mm:ss[.fff].

Aggiornamento:

È anche possibile impostare il tipo di dati del parametro ADO per adDBTimeStamp te stesso. ADODB lo imposta su adDate quando si utilizza ftDateTime.

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

La conversione in stringa interrompe semplicemente l'app. adDBTimeStamp funziona però. Ma ora il mio codice di mappatura è rotto, e sto ricevendo EOleException: "L'applicazione utilizza un valore del tipo sbagliato per l'operazione corrente". Ma sospetto che questo sia un problema con le varianti. Grazie per la risposta! – whosrdaddy

0

Ho avuto lo stesso problema con vfpoledb (il driver di Visual FoxPro), ma il trucco con adDBTimeStamp non ha funzionato. FWIW, questo è VFPOLEDB 9.0.0.5815 con Delphi 7 e anche con RAD Studio 10 (Seattle).

Nel caso di VFPOLEDB esiste un'altra soluzione alternativa che si basa sul fatto che in Fox la rappresentazione sottostante per i valori di data/ora (tipo 'T') è uguale a quella dei valori di data (tipo 'D ') passato da OLEDB, ovvero un doppio a 64 bit in cui la parte intera rappresenta giorni dal 1 ° gennaio dell'anno 0001 e la parte frazionaria rappresenta l'ora del giorno.

Se il campo tabella ha tipo "T" e OLEDB passa una data, Fox forza il valore senza fare nulla. Quindi tutto ciò che è necessario qui è l'aggiunta nella parte frazionaria.

esempio, con start_time essendo un campo di data/ora:

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

Questo dà 2016-05-22 00:00:00 nella tabella.

Ora, aggiungere la parte frazionaria in questo modo:

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; 

Questo dà 2016-05-22 02:17:42. Anche i secondi frazionari vengono preservati, sebbene non vengano visualizzati.

Problemi correlati