«0.0» не является действительной отметкой времени при доступе к OldValue TDateField TClientDataSet - PullRequest
2 голосов
/ 05 мая 2009

Когда я использую следующий код во вновь вставленной записи в TClientDataSet:

cdsMyDateField.OldValue <> Null

Я получаю EConvertError:

''0.0' is not a valid timestamp'.

Глядя на код VCL Delphi, он пытается преобразовать значение в TDateTime, что приводит к этому исключению, поскольку значение (Null) является недопустимым DateTime, но, сравнивая варианты, я подумал, что он вернет вариант, который в этом случае будет нулевым, но этого не происходит, вместо этого я получаю это исключение.

Я знаю, что могу просто проверить, если DataSet.State = dsInsert перед сравнением значений, как если бы State = dsInsert, каждое OldValue равно Null, но я хочу понять, почему OldValue пытается преобразовать значение вместо простого возврата Null во всех полях, когда State = dsInsert.

Может кто-нибудь дать мне немного света?

Ответы [ 6 ]

5 голосов
/ 27 мая 2009

FWIW, я столкнулся с этой же проблемой, и это вызвало у меня несколько головных болей. Мое мнение: поведение противоречиво, поэтому даже по одной этой причине я классифицирую его как ошибку. Это также ошибка, потому что создание исключения при чтении свойства является ИМХО нелепым и не соответствует назначению свойств (принцип наименьшего удивления). Я бы ожидал, что OldValue будет неназначенным, а не вызовет исключение при чтении. (Кроме того, тот факт, что какая-то проблема существовала в течение длительного времени, не означает, что это ошибка.)

(Изменить: Обновление моего ответа с дополнительной информацией, включая наш обходной путь. Также опубликовано то же самое в отчете QC: http://qc.embarcadero.com/wc/qcmain.aspx?d=73852)

У нас также была такая же проблема в приложении, которое интенсивно использует наборы данных / клиентские наборы данных. Однако настоящая проблема заключается не в наборе данных клиента, а в процедуре проверки метки времени в SysUtils, которая, по-видимому, ошибочно проверяет метку времени со значением 0,0 как недопустимую.

Поэтому обходной путь / исправление относится к SysUtils и подпрограмме проверки, а не к Tclientdataset. В частности, в подпрограмме проверки «ValidateTimeStamp ()» часть времени корректно сравнивается <0, но часть даты ошибочно сравнивается <= 0 . </p>

Следовательно, (допустимое) значение 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;
2 голосов
/ 05 мая 2009

TDateTime в Delphi - это двойное число, где дата сохраняется в целой части числа, а время - в дробной части. Таким образом, преобразование пустого значения даты в 0.0 является в некоторой степени правильным. Поскольку базовое поле, к которому вы обращаетесь, является TDateField (или полем TDateTime), оно, вероятно, выполняет преобразование внутри.

Кроме того, проверка варианта против Null больше не подходит в Delphi. Вариант Null все еще назначен, но имеет значение Null, тогда как неназначенный вариант не имеет значения. (Подумайте о пустом значении базы данных SQL). Вместо этого используйте функцию VarIsNull (const V: Variant), находящуюся в модуле Variants.pas; он возвращает true, если вариант имеет значение null, и false, если он имеет какое-либо другое значение.

1 голос
/ 21 октября 2010

Это ошибка в Midas / TClientDataSet, связанная с буферами записи. Когда вы делаете AppendData много раз, поля InternalCalc отображаются как «не нулевые» (неправильный нулевой флаг в буфере записи). В Delphi 2010 мы можем изучить источники Midas.dll (файлы .cpp).

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

Я изучаю, как реализовать решение.

Аль Гонсалес.

1 голос
/ 06 мая 2009

Я отладил код ниже с активированной опцией Debug DCUs и странная вещь в том, что SysUtils.ValidateTimeStamp оценивает TimeStamp с датой = 0, чтобы быть недействительным и, следовательно, выбрасывая Исключение EConvertError (вместо возврата Null или Unassigned).

Таким образом, конечным результатом является выполнение запроса OldValue для нулевого поля в состояние dsInsert является недействительным. Вариант НИКОГДА не возвращен так что это не имеет значения, если вы тестируете его с помощью (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, 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. Поля создаются во время разработки.

0 голосов
/ 21 августа 2012

Я использую Delphi 2007 с MySQL, и мое решение:

В запросе MySQL я использовал:

select CAST(dateField AS CHAR) from table_name

... используя CAST, я преобразовал поле в строковое значение и могу проверить, является ли значение 0000-00-00 ... и после этого использовать действительное значение поля.

0 голосов
/ 06 мая 2009

Получил его в 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 преобразует базовые данные в собственный формат данных, выполняя проверки достоверности там, где это необходимо

Редактировать : в результате комментария Фабрицио я подчеркнул, что OldValue технически недействителен после вставки. Так что технически это не может быть ошибкой.

Его «новые доказательства» можно проверить, проверив источники VCL / RTL:

Для типов полей ftDate, ftTime, ftDateTime, TDataSet.DataConvert вызывает свою локальную функцию NativeToDateTime, которая заполняет TimeStamp, а затем преобразует это с помощью SysUtils.TimeStampToDateTime, которое, в свою очередь, вызывает SysUtils.ValidateTimeStamp, которое вызывает ноль, если исключение Time меньше, чем Time, или часть даты меньше или равна нулю.

Часть Date может стать нулевой только для типов полей ftDate и ftDateTime (TDateField и TDateTimeField), следовательно, только они могут вызывать исключение. Для всех других типов данных NativeToDateTime не будет иметь проблем: все эти типы допускают результат, заполненный нулевыми байтами.

Я только что проверил историю RTL / VCL: поскольку Delphi 6 SysUtils.TimeStampToDateTime вызывает SysUtils.ValidateTimeStamp, так что это поведение остается тем же с 2001 года.

Из-за этого очень трудно расценивать это как ошибку.

...