2011-12-30 18 views
5

Tôi tình cờ gặp vấn đề này ngày hôm qua khi tôi bận viết một số bài kiểm tra đơn vị bằng cách sử dụng SQLLite. Môi trường của tôi là Windows7/Delphi XE.Sử dụng tham số datetime với ADO (ODBC) mất phần thời gian

Sử dụng TADOQuery cùng với thông số TDateTime dẫn đến mất phần thời gian.

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; 

Điều thú vị là, khi tôi nhận xét dòng Qry.Parameters.ParseSQL(Qry.SQL.Text, True); Nó sẽ hoạt động tốt. Tôi cần phần ParseSQL vì tôi đang xây dựng một ORM mini nên cần phải biết các tham số nào phải được ánh xạ. Một số quan sát:

  • Thực hiện cùng một thử nghiệm với MySQL5 cho thấy cùng một vấn đề (bất kể phần ParseSQL).
  • Mã này hoạt động với SQL Server và trình điều khiển OLEDB.

Tôi đã tìm kiếm trên net và tìm thấy một số liên kết thú vị:

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

Các liên kết đầu tiên cho thấy 'sửa chữa' ADODB.pas, cái gì tôi làm không muốn làm. Đọc liên kết cuối cùng, có vẻ như ADO ánh xạ giá trị ngày giờ cho đến nay.

trả lời tôi không muốn nghe: sử dụng một thư viện/phần (như dbExpress, Zeoslib, ...)

Tôi không chắc chắn điều gì sẽ là cách tiếp cận hợp lý nhất để giải quyết vấn đề này.

Vì Linas và Marjan Venema đề xuất tôi có thể bỏ qua phần ParseSQL. Vì vậy, mã hoạt động ngay bây giờ với SQLlite IF Tôi bỏ qua dòng Qry.Parameters.ParamByName('d').DataType := ftDateTime;.

Nhưng MySQL từ chối tiết kiệm thời gian. Tôi có thấy một vấn đề tương thích giữa ADO và ODBC của MySQL ở đây không?

+0

Tôi muốn đặt datatype _before_ đặt giá trị, nhưng không chắc chắn sẽ tạo ra bất kỳ sự khác biệt nào. Tại sao xây dựng một ORM mini cần ParseSQL? Tôi đã xây dựng một số loại ORM libs và không bao giờ cần nó? IIRC Qry.SQL.Prepare cũng nên điền tập hợp tham số. –

+0

Tôi cần biết những thông số nào tôi có trong Truy vấn của tôi và các giá trị sẽ được ánh xạ từ một đối tượng bằng rtti. bạn có nghĩa là Qry.Prepared: = true; ? không có sự khác biệt. – whosrdaddy

+1

@whosrdaddy Không cần phải tự mình gọi ParseSQL. Nó được gọi tự động khi các thay đổi SQL.Text (ParamCheck phải là True). – Linas

Trả lời

8

Tôi đã thử nghiệm này một chút bằng cách sử dụng SQL Server và có cùng một vấn đề chính xác khi tôi sử dụng MSDASQL.1 (ODBC). Mã của bạn hoạt động tốt với SQLOLEDB.1 và SQLNCLI10.1.

Nếu bạn chỉ định loại tham số là ftString, nó sẽ tiết kiệm thời gian bằng ODBC, (ít nhất là trên SQL Server).

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

Lưu ý: Hãy cẩn thận với cài đặt cục bộ khi bạn sử dụng DateTimeToStr nó có thể không tạo ra những gì bạn muốn. Đặt cược an toàn sẽ là sử dụng yyyy-mm-dd hh:mm:ss[.fff].

Cập nhật:

Bạn cũng có thể thiết lập các kiểu dữ liệu của tham số ado để adDBTimeStamp mình. ADODB đặt nó thành adDate khi bạn sử dụng ftDateTime.

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

Chuyển đổi thành chuỗi chỉ làm treo ứng dụng. adDBTimeStamp hoạt động mặc dù. Nhưng bây giờ mã lập bản đồ của tôi bị hỏng và tôi nhận được EOleException: "Ứng dụng sử dụng giá trị sai loại cho hoạt động hiện tại". Nhưng tôi nghi ngờ đây là một vấn đề với các biến thể. Cảm ơn bạn đã trả lời! – whosrdaddy

0

Tôi gặp vấn đề tương tự với VFPOLEDB (trình điều khiển Visual FoxPro) nhưng thủ thuật với adDBTimeStamp không hoạt động. FWIW, đó là VFPOLEDB 9.0.0.5815 với Delphi 7 và cũng với RAD Studio 10 (Seattle).

Trong trường hợp VFPOLEDB có một giải pháp khác có thể dựa trên thực tế rằng trong Fox biểu diễn cơ sở cho các giá trị ngày/thời gian (loại 'T') giống như giá trị ngày tháng (loại 'D ') được thông qua bởi OLEDB, tức là một đôi 64-bit, nơi phần nguyên đại diện cho các ngày kể từ ngày 1 tháng 1 trong năm 0001 và phần phân số đại diện cho thời gian trong ngày.

Nếu trường bảng có loại 'T' và OLEDB vượt qua một ngày thì Fox sẽ ép buộc giá trị bằng cách không làm gì cả. Vì vậy, tất cả những gì cần thiết ở đây là thêm trở lại trong phần phân đoạn.

Ví dụ, với start_time trở thành một lĩnh vực ngày/giờ:

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

Điều này cho phép 2016-05-22 00:00:00 trong bảng.

Bây giờ, thêm các phần phân đoạn như thế này:

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; 

Điều này cho phép 2016-05-22 02:17:42. Ngay cả phân số giây cũng được giữ nguyên, mặc dù chúng không được hiển thị.