0

We are using C++Builder 6 for an old product, and ran into a problem with comparison of DateTime objects failing if they contain milliseconds.

This problem seems to be present even in Visual Studio C#, but one of our colleagues already found the cause and a solution there.

The problem is that, after the query is created, it will use datetime2 as a parameter, however, comparisons with datetime2 will fail if the time contains milliseconds. In Visual Studio C#, this was solved by manually manipulating the query in the events:

var dbParameters = command.Parameters.OfType<DbParameter>().Where(p => p.DbType == DbType.DateTime2);
foreach(var parameter in dbParameters)
{
    parameter.DbType = DbType.DateTime;
}

Basically, what it does is changes a query from this:

exec sp_executesql N'select *
from Table
where Date = @P1
',N'@P1 datetime2(0)','2022-05-18 15:24:17.840'

To this:

exec sp_executesql N'select *
from Table
where Date = @P1
',N'@P1 datetime','2022-05-18 15:24:17.840'

Note: The former query will not return anything, while the latter returns the expected result.

I need a similar solution for C++Builder 6, but I can't seem to find any place where I can even access these parameters.

I tried the TADOConnection.OnWillExecute event, but it seems to pass only the CommandText with ? in place of the parameters, but not the parameters themselves.

Is there any place where I can do such a query manipulation in C++Builder 6 with ADO components?

Sample project: UntDateTimeTestMain.cpp

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "UntDateTimeTestMain.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TFrmDateTimeTestMain *FrmDateTimeTestMain;
//---------------------------------------------------------------------------
__fastcall TFrmDateTimeTestMain::TFrmDateTimeTestMain(TComponent* Owner)
    : TForm(Owner)
{
    bConnect->Focused();
}
//---------------------------------------------------------------------------
void __fastcall TFrmDateTimeTestMain::bConnectClick(TObject *Sender)
{
    bool ConnectionOK = false;

    ADOConnection->ConnectionString = "Provider=MSOLEDBSQL;Password=" + eDbPassword->Text
        + ";Persist Security Info=True;User ID=" + eDbUser->Text
        + ";Initial Catalog=" + eDatabase->Text
        + ";Data Source=" + eServer->Text
        + ";OLE DB Services = -2;Application Name=DateTimeTestTool";

    ADOConnection->Connected = true;
    ADOQueryBgTasks->Open();
}
//---------------------------------------------------------------------------
void __fastcall TFrmDateTimeTestMain::ADOQueryBgTasksAfterOpen(
      TDataSet *DataSet)
{
    lEntryTime->Caption = "EntryTime:  " + ADOQueryBgTasks->FieldByName("EntryTime")->AsString;
}
//---------------------------------------------------------------------------
void __fastcall TFrmDateTimeTestMain::bUpdateClick(TObject *Sender)
{
    //ADOQueryBgTasks->Post();
    ADOQueryUpdateBgTasks->Parameters->ParamByName("Command")->Value = dbeCommand->Text;
    ADOQueryUpdateBgTasks->Parameters->ParamByName("EntryTime")->Value = ADOQueryBgTasks->FieldByName("EntryTime")->AsDateTime;
    ADOQueryUpdateBgTasks->ExecSQL();
}
//---------------------------------------------------------------------------

UntDateTimeTestMain.dfm

object FrmDateTimeTestMain: TFrmDateTimeTestMain
  Left = 765
  Top = 234
  Width = 314
  Height = 314
  Caption = 'DateTime Test Tool'
  Color = clBtnFace
  Constraints.MinHeight = 314
  Constraints.MinWidth = 314
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  Position = poScreenCenter
  DesignSize = (
    306
    283)
  PixelsPerInch = 96
  TextHeight = 13
  object Label1: TLabel
    Left = 16
    Top = 16
    Width = 34
    Height = 13
    Caption = 'Server:'
  end
  object Label2: TLabel
    Left = 16
    Top = 48
    Width = 49
    Height = 13
    Caption = 'Database:'
  end
  object Label3: TLabel
    Left = 16
    Top = 80
    Width = 25
    Height = 13
    Caption = 'User:'
  end
  object Label4: TLabel
    Left = 16
    Top = 112
    Width = 49
    Height = 13
    Caption = 'Password:'
  end
  object Label5: TLabel
    Left = 16
    Top = 144
    Width = 50
    Height = 13
    Caption = 'Command:'
  end
  object lEntryTime: TLabel
    Left = 16
    Top = 176
    Width = 50
    Height = 13
    Caption = 'EntryTime:'
  end
  object eServer: TEdit
    Left = 72
    Top = 12
    Width = 222
    Height = 21
    Anchors = [akLeft, akTop, akRight]
    TabOrder = 1
  end
  object eDatabase: TEdit
    Left = 72
    Top = 44
    Width = 222
    Height = 21
    Anchors = [akLeft, akTop, akRight]
    TabOrder = 2
  end
  object eDbUser: TEdit
    Left = 72
    Top = 76
    Width = 131
    Height = 21
    Anchors = [akLeft, akTop, akRight]
    TabOrder = 3
  end
  object eDbPassword: TEdit
    Left = 72
    Top = 108
    Width = 131
    Height = 21
    Anchors = [akLeft, akTop, akRight]
    PasswordChar = '*'
    TabOrder = 4
  end
  object bConnect: TButton
    Left = 218
    Top = 104
    Width = 75
    Height = 25
    Anchors = [akTop, akRight]
    Caption = 'Connect'
    TabOrder = 0
    OnClick = bConnectClick
  end
  object dbeCommand: TDBEdit
    Left = 72
    Top = 140
    Width = 222
    Height = 21
    Anchors = [akLeft, akTop, akRight]
    DataField = 'Command'
    DataSource = DataSource1
    TabOrder = 5
  end
  object bUpdate: TButton
    Left = 218
    Top = 200
    Width = 75
    Height = 25
    Caption = 'Update'
    TabOrder = 6
    OnClick = bUpdateClick
  end
  object ADOConnection: TADOConnection
    CommandTimeout = 300
    LoginPrompt = False
    Provider = 'MSOLEDBSQL'
    Left = 208
    Top = 72
  end
  object ADOQueryBgTasks: TADOQuery
    Connection = ADOConnection
    AfterOpen = ADOQueryBgTasksAfterOpen
    Parameters = <>
    SQL.Strings = (
      'SELECT * FROM BGTasks')
    Left = 240
    Top = 72
  end
  object DataSource1: TDataSource
    DataSet = ADOQueryBgTasks
    Left = 272
    Top = 72
  end
  object ADOQueryUpdateBgTasks: TADOQuery
    Connection = ADOConnection
    Parameters = <
      item
        Name = 'Command'
        Size = -1
        Value = Null
      end
      item
        Name = 'EntryTime'
        Size = -1
        Value = Null
      end>
    SQL.Strings = (
      'UPDATE BGTasks'
      'SET Command = :Command'
      'WHERE EntryTime = :EntryTime')
    Left = 184
    Top = 192
  end
end

UntDateTimeTestMain.h

//---------------------------------------------------------------------------

#ifndef UntDateTimeTestMainH
#define UntDateTimeTestMainH
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ADODB.hpp>
#include <DB.hpp>
#include <DBCtrls.hpp>
#include <Mask.hpp>
//---------------------------------------------------------------------------
class TFrmDateTimeTestMain : public TForm
{
__published:    // Von der IDE verwaltete Komponenten
    TADOConnection *ADOConnection;
    TLabel *Label1;
    TEdit *eServer;
    TLabel *Label2;
    TLabel *Label3;
    TLabel *Label4;
    TEdit *eDatabase;
    TEdit *eDbUser;
    TEdit *eDbPassword;
    TButton *bConnect;
    TADOQuery *ADOQueryBgTasks;
    TDBEdit *dbeCommand;
    TLabel *Label5;
    TDataSource *DataSource1;
    TLabel *lEntryTime;
    TButton *bUpdate;
    TADOQuery *ADOQueryUpdateBgTasks;
    void __fastcall bConnectClick(TObject *Sender);
    void __fastcall ADOQueryBgTasksAfterOpen(TDataSet *DataSet);
    void __fastcall bUpdateClick(TObject *Sender);
private:    // Anwender-Deklarationen
public:     // Anwender-Deklarationen
    __fastcall TFrmDateTimeTestMain(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TFrmDateTimeTestMain *FrmDateTimeTestMain;
//---------------------------------------------------------------------------
#endif

Resulting query:

exec sp_executesql N'UPDATE BGTasks
SET Command = @P1
WHERE EntryTime = @P2
',N'@P1 varchar(30),@P2 datetime2(0)','EXEC St_Proc_Download_Location','2022-02-07 10:50:50'

As you can see it results in using "datetime2(0)" as type and on top of that does not even pass the milliseconds in the time (the actual time in the database is 2022-02-07 10:50:50.487).

I tried changing the type from unknown to DateTime but it results in exactly the same query.

Benjamin
  • 1
  • 1
  • How are you preparing and executing the SQL in C++Builder to begin with? `TADOCommand`, `TADOQuery`, `TADOStoredProc`, they all have a `Parameters` property. But even in the `OnWillExecute` event, you are given a `_Command*` interface pointer to the ADO [`Command`](https://learn.microsoft.com/en-us/sql/ado/reference/ado-api/command-object-ado) object that wants to execute the SQL, and it has a [`Parameters`](https://learn.microsoft.com/en-us/sql/ado/reference/ado-api/parameters-collection-ado) collection. – Remy Lebeau Jun 07 '22 at 22:12
  • Hi Remy, we use TADOQuery to execute queries and yes, they have the parameters defined. However this is before the provider translates the parameter type datetime to "datetime2(0)". The Command object is NULL by the way, only CommandText has data in OnWillExecute. Parameters are replaced with ? there. – Benjamin Jun 08 '22 at 08:03
  • OK, so what is stopping you from specifying the desired parameter type when preparing the SQL query up front? Can you show your actual `TADOQuery` code? – Remy Lebeau Jun 08 '22 at 21:22
  • I don't think specifying the parameter type will prevent it from being converted to datetime2(0) in the query, but I think I'll create a small prototype to test that. It still won't be a solution for me because even if that worked it would require to update every TADOQuery. I'm looking for a solution that applies to all queries in the application. – Benjamin Jun 10 '22 at 09:35
  • I added a sample project now. As I feared, manually setting the parameter type doesn't help, it's still using datetime2(0) in the query that I fetch with SQL Server Profiler. I also noticed that the value does not even include milliseconds in the first place. – Benjamin Jun 16 '22 at 10:55

0 Answers0