2

当我在 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 尝试转换值而不是全部返回 Null State = dsInsert 时的字段。

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

4

6 回答 6

5

FWIW,我遇到了同样的问题,这让我很头疼。我的意见:这种行为是不一致的,所以即使仅从这个原因我也将其归类为错误。这也是一个错误,因为在读取属性时引发异常是恕我直言,这很荒谬,并且不符合属性的意图(最不意外的原则。)我希望 OldValue 是未分配的,而不是在读取时引发异常。(此外,某些问题已经存在很长时间这一事实并不意味着它是否是一个错误。)

(编辑:用更多信息更新我的答案,包括我们的解决方法。也发布到 QC 报告中:http: //qc.embarcadero.com/wc/qcmain.aspx ?d=73852 )

我们在一个大量使用数据快照/客户端数据集的应用程序中也遇到了同样的问题。然而,实际问题不在于客户端数据集,而在于 SysUtils 中的时间戳验证例程,该例程显然错误地将值为 0.0 的时间戳验证为无效。

因此,解决方法/修复是针对 SysUtils 和验证例程,而不是针对 Tclientdataset 具体而言,在验证例程“ValidateTimeStamp()”中,时间部分正确比较 < 0,但日期部分错误比较 <= 0。

因此,(合法的)0.0 日期时间值有时会转换为 datepart = 0 的时间戳,并且当再次反向验证该值时(例如从此处和 QC 报告中所示的数据集字段读取值时), (错误地)引发了异常。因此,将时间戳的日期部分的验证更改为使用严格小于的简单修复,修复了 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;
于 2009-05-27T10:42:46.010 回答
2

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

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

于 2009-05-05T19:46:58.353 回答
1

我在激活 Debug DCUs 选项的情况下调试了下面的代码,奇怪的是 SysUtils.ValidateTimeStamp 将 date = 0 的 TimeStamp 评估为无效,因此抛出 EConvertError 异常(而不是返回 Null 或 Unassigned)。

所以最终结果是对 dsInsert 状态下的空字段执行 OldValue 请求是无效的。该变体永远不会返回,因此如果您使用 (field.OldValue <> 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、浮点数和备忘录)进行测试,并且 OldValue 未分配 - 所以上面的测试将评估为假。

似乎只有 TDateField 和 TDateTimeField 显示该行为。TTimeField 和 TSQLTimeStamp 评估正常 - 但 TSQLTimeStampField.OldValue 既不等于 Null 也不等于 Unassigned (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。这些字段是在设计时创建的。

于 2009-05-05T20:03:59.250 回答
1

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

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

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

阿尔冈萨雷斯。

于 2010-10-21T18:36:15.333 回答
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 来源来验证:

对于字段类型 ftDate、ftTime、ftDateTime,TDataSet.DataConvert 调用其本地 NativeToDateTime 函数,该函数填充 TimeStamp,然后使用 SysUtils.TimeStampToDateTime 转换它,然后调用 SysUtils.ValidateTimeStamp,当时间部分小于零时引发异常,或者日期部分小于或等于零。

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

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

这使得很难将其视为错误。

于 2009-05-06T10:07:42.107 回答
0

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

在我使用的 MySQL 查询中:

select CAST(dateField AS CHAR) from table_name

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

于 2012-08-21T19:00:04.620 回答