访问TClientDataSet的TDateField的OldValue时,“0.0”不是有效的时间戳

时间:2009-05-05 18:17:17

标签: delphi delphi-2009 vcl

当我在TClientDataSet中新插入的记录中使用以下代码时:

cdsMyDateField.OldValue <> Null

我收到了EConvertError:

''0.0' is not a valid timestamp'.

看看Delphi的VCL代码,它试图将值转换为TDateTime,这导致了这个异常,因为值(Null)是一个无效的DateTime,但是当我比较Variants时我认为它会返回一个变体,在这种情况下将是Null,但这不会发生,而是我得到这个例外。

我知道我可以在比较值之前检查DataSet.State = dsInsert,就像State = dsInsert每个OldValue都是Null一样,但我想理解为什么OldValue尝试转换值而不是Just返回当State = dsInsert时,在所有字段中为空。

任何人都可以给我一些启示吗?

6 个答案:

答案 0 :(得分:5)

FWIW,我遇到了同样的问题,这让我头疼不已。我的观点:行为是不一致的,所以即使仅仅因为这个原因,我把它归类为一个bug。这也是一个错误,因为在阅读一个属性时引发异常是恕我直言,并且不符合属性的意图(最不出意的原则。)我希望OldValue是Unassigned,不会在读取时引发异常。 (此外,某些问题已存在很长时间并不意味着它是否是一个错误。)

(编辑:使用更多信息更新我的回答,包括我们的解决方法。同样发布在质量控制报告中:http://qc.embarcadero.com/wc/qcmain.aspx?d=73852

我们在一个大量使用datasnap / clientdatasets的应用程序中也遇到了同样的问题。然而,实际问题不在于clientdataset,而是使用SysUtils中的Timestamp验证例程,显然错误地验证了0.0值为无效的Timestamp。

因此,解决方法/修复是SysUtils和验证例程,而不是Tclientdataset。具体来说,在验证例程“ValidateTimeStamp()”中,正确比较时间部分&lt; 0,但日期部分被错误地比较&lt; = 0.

因此,(合法的)0.0 Datetime值有时会转换为datepart = 0的时间戳,并且当该值再次反向验证时(例如,从此处和QC中显示的数据集字段读取值时)报告),(错误地)提出了一个例外。因此,修改时间戳的Date部分的验证以使用严格的小于,修复TClientDataset公开的问题的简单修复。

这是我们的解决方法,基于Delphi 7.1

(* SysUtils.pas line 10934 (Delphi 7.1)  *)
(**)(* fix - Timestamp values 0.0 erroneously designated as invalid *)
(* D7.1 *)
(* Walter Prins, originally patched May 2005, submitted 4 June 2009 *)
procedure ValidateTimeStamp(const TimeStamp: TTimeStamp);
begin
  if (TimeStamp.Time < 0) or (TimeStamp.Date < 0) then (* Changed TimeStamp.Date <= 0 to TimeStamp.Date < 0 *)
    ConvertErrorFmt(@SInvalidTimeStamp, [TimeStamp.Date, TimeStamp.Time]);
end;

答案 1 :(得分:2)

Delphi中的TDateTime是double,其中日期存储在整数部分中,时间存储在小数部分中。因此,在某种程度上,将空日期值转换为0.0是正确的。由于您正在访问的基础字段是TDateField(或TDateTime字段),因此它可能在内部进行转换。

此外,检查Variant对Null不再是Delphi中的正确方法。仍然分配了Null变量,但其值为Null,而未分配的变量没有值。 (想想SQL数据库的NULL值)。改为使用Variants.pas单元中的VarIsNull(const V:Variant)函数;如果变量为null,则返回true;如果变量为null,则返回false。

答案 2 :(得分:1)

我调试了下面的代码,激活了Debug DCU选项 而奇怪的是SysUtils.ValidateTimeStamp会进行评估 日期= 0的TimeStamp无效,因此抛出一个 EConvertError异常(而不是返回Null或Unassigned)。

因此最终结果是对一个空字段执行OldValue请求 dsInsert状态是它的无效。变体从不返回 所以如果用(field.OldValue&lt;&gt; Null)或者测试它是无关紧要的 VarIsNull(field.OldValue)。之前会抛出异常。

cds_something有两个字段(在设计时创建):

  • dt_Something
  • num_something

代码:

var
  b: TClientDataset;
begin
  b := cds_somethin;
  b.Close;
  b.CreateDataSet;
  b.Insert;
  if b.FieldByName('DT_Sometinhg').OldValue <> Null then
    ShowMessage('Something wrong!!!')
  else
    ShowMessage('Normal');

  b.Cancel;

注意:我搞砸了这篇文章的原始编辑。这就是现在 正确解释我所发现的。

添加:使用其他一些字段类型(字符串,BCD,Float和Memo)和OldValue进行测试 未分配 - 因此上面的测试将评估为false。

出现只有TDateField和TDateTimeField显示该行为。 TTimeField和TSQLTimeStamp评估正常 - 但TSQLTimeStampField.OldValue不等于 为空或未分配(wtf !!)...

该片段略有改变:

var
  b: TClientDataset;
begin
  b := cds_somethin;
  b.Close;
  b.CreateDataSet;
  b.Insert;
  /*
  if (b.FieldByName('DT_Something').OldValue <> Null) 
     and (b.FieldByName('DT_Something').OldValue <> Unassigned)  then
    ShowMessage('Something wrong!!!')
  else
    ShowMessage('Normal');
  */
  if (b.FieldByName('ts_Something').OldValue <> Null) 
     and (b.FieldByName('ts_Something').OldValue <> Unassigned)  then
    ShowMessage('Something wrong!!!')
  else
    ShowMessage('Normal');


  b.Cancel;

其中ts_Something是TSQLTimeStampField。这些字段是在设计时创建的。

答案 3 :(得分:1)

这是与记录缓冲区相关的Midas / TClientDataSet的错误。当您多次执行AppendData时,InternalCalc字段显示为“not null”(记录缓冲区中的错误空标志)。在Delphi 2010中,我们可以研究Midas.dll源(.cpp文件)。

http://img514.imageshack.us/img514/2840/wrongnull.jpg

我正在研究如何实施解决方案。

Al Gonzalez。

答案 4 :(得分:0)

在Delphi 2009 update 2和Delphi 2007中重现如下:

uses DB, DBClient;
procedure TTestForm1.TestButtonClick(Sender: TObject);
const
  SMyDateField = 'MyDateField';
  SMyIntegerField = 'MyIntegerField';
var
  MyClientDataSet: TClientDataSet;
  MyClientDataSetMyDateField: TField;
  MyClientDataSetMyIntegerField: TField;
  OldValue: Variant;
begin
  MyClientDataSet := TClientDataSet.Create(Self);
  MyClientDataSet.FieldDefs.Add(SMyDateField, ftDate);
  MyClientDataSet.FieldDefs.Add(SMyIntegerField, ftInteger);
  MyClientDataSet.CreateDataSet();
  MyClientDataSetMyDateField := MyClientDataSet.FieldByName(SMyDateField);
  MyClientDataSetMyIntegerField := MyClientDataSet.FieldByName(SMyIntegerField);
  MyClientDataSet.Insert();
  OldValue := MyClientDataSetMyIntegerField.OldValue;
  OldValue := MyClientDataSetMyDateField.OldValue;
end;

您总是会收到此错误:

exception class EConvertError with message ''0.0' is not a valid timestamp'.

我不确定这是否被视为一个错误:

    插入时
  • 技术上没有OldValue,因此获取它可能会引发异常
  • MyClientDataSetMyIntegerField.OldValue返回0,但MyClientDataSetMyDateField.OldValue引发异常

更多说明:

  • TCustomClientDataSet.GetFieldData将获取实际的基础数据
  • TDataSet.DataConvert会将基础数据转换为本机数据格式,并在需要时执行有效性检查

编辑:由于Fabricio的评论,我强调OldValue在插入后技术上无效。从技术上讲,这可能不是一个错误。

他的'新证据'可以通过检查VCL / RTL来源来验证:

对于fieldtypes ftDate,ftTime,ftDateTime,TDataSet.DataConvert调用其填充TimeStamp的本地NativeToDateTime函数,然后使用SysUtils.TimeStampToDateTime转换它,SysUtils.TimeStampToDateTime又调用SysUtils.ValidateTimeStamp,当Time部分小于零时引发异常,或日期部分小于或等于零。

对于字段类型ftDate和ftDateTime(TDateField和TDateTimeField),Date部分可以变为零,因此只有那些可以引发异常。 对于所有其他数据类型,NativeToDateTime不会出现问题:这些类型都允许填充零字节的结果。

我刚检查了RTL / VCL历史记录:由于Delphi 6 SysUtils.TimeStampToDateTime调用了SysUtils.ValidateTimeStamp,所以这种行为自2001年以来就一直如此。

这使得很难将此视为一个错误。

答案 5 :(得分:0)

我正在使用delphi 2007和MySQL,我的解决方案是:

在MySQL查询中我使用过:

select CAST(dateField AS CHAR) from table_name

...使用CAST我将字段转换为字符串值,我可以测试值是否为0000-00-00 ...之后使用字段的实际值。