Delphi - Изменить поле на вычисляемое поле во время выполнения. Это хорошая практика?

Как и в названии вопроса, я спорю с коллегой о том, как должны использоваться вычисляемые поля. Насколько мне известно, вычисляемые поля создаются во время выполнения, как в ответе Франсуа на вопрос " Добавление вычисляемого поля в запрос во время выполнения". На этот же вопрос есть другой ответ от sabri.arslan, который предлагает изменить существующее поле на расчетное (код ниже)

var
 initing:boolean;

procedure TSampleForm.dsSampleAfterOpen(
  DataSet: TDataSet);
var
 i:integer;
 dmp:tfield;
begin
if not initing then
 try
  initing:=true;
  dataset.active:=false;
  dataset.FieldDefs.Update;
  for i:=0 to dataset.FieldDefs.Count-1 do
  begin
   dmp:=DataSet.FieldDefs.Items[i].FieldClass.Create(self);
   dmp.FieldName:=DataSet.FieldDefs.Items[i].DisplayName;
   dmp.DataSet:=dataset;
   if (dmp.fieldname='txtState') or (dmp.FieldName='txtOldState') then
   begin
     dmp.Calculated:=true;
     dmp.DisplayWidth:=255;
     dmp.size:=255;
   end;
  end;
  dataset.active:=true;
 finally
  initing:=false;
 end;
end;

procedure TSampleForm.dsSampleAfterClose(
  DataSet: TDataSet);
var
 i:integer;
 dmp:TField;
begin
if not initing then
begin
 for i:=DataSet.FieldCount-1 downto 0 do
 begin
  dmp:=pointer(DataSet.Fields.Fields[i]);
  DataSet.Fields.Fields[i].DataSet:=nil;
  freeandnil(dmp);
 end;
 DataSet.FieldDefs.Clear;
end;
end;

procedure TSampleForm.dsSampleCalcFields(
  DataSet: TDataSet);
var
 tmpdurum,tmpOldDurum:integer;
begin
  if not initing then
    begin
      tmpDurum := dataset.FieldByName( 'state' ).AsInteger;
      tmpOldDurum:= dataset.FieldByName( 'oldstate' ).AsInteger;
      dataset.FieldByName( 'txtState' ).AsString := State2Text(tmpDurum);
      dataset.FieldByName( 'txtOldState' ).AsString := State2Text(tmpOldDurum);
    end;
end;

procedure TSampleForm.btnOpenClick(Sender: TObject);
begin
 if dsSample.Active then
   dsSample.Close;
 dsSample.SQL.text:='select id,state,oldstate,"" as txtState,"" as txtOldState from states where active=1';
 dsSample.Open;
end;

Я считаю, что это изменение приводит к неизвестному поведению указанного TField. Безопасно ли изменять поле набора данных на вычисленное во время выполнения? Какие проблемы это может вызвать?

Л.Э.: Это вопрос. Его цель - продемонстрировать хорошую практику добавления вычисляемого поля в набор данных во время выполнения. И да, добавление вычисляемого поля во время выполнения - плохой дизайн.

LE2: Это всего лишь пример "не делай так". В качестве аргумента я спросил, каково поведение поля в обсуждении после этого. Как это поле будет действовать?

3 ответа

Решение

Нет, это не очень хорошая практика. Простой факт того, что код является сложным, предполагает, что такой практики следует избегать. Кто-то уже ссылался на принцип KISS, и я согласен с этим.

В частности, одного простого факта, что набор данных должен быть открыт дважды, достаточно, чтобы заставить меня не любить эту практику.

Кроме того, изменение природы поля с данных на вычисленные изменит способ, которым набор данных организует поля во внутреннем представлении записи (то, что набор данных называет буфером записи). Такое представление может сильно отличаться от одной реализации набора данных к другой. Поскольку вопрос не идентифицирует конкретный набор данных, изменения в поведении (в общем):

  1. Поле данных будет хранить свое значение в структуре, принадлежащей базовому клиенту базы данных; вычисляемое поле будет хранить свое значение в непостоянном буфере;
  2. Во время открытия набора данных существует процесс, называемый связыванием полей, который состоит в связывании полей данных с соответствующей структурой клиента базы данных; когда это связывание не удается, набор данных обычно вызывает исключение; вычисляемые поля не принимают участия в этом процессе, потому что они используют внутренний буфер полей для хранения своих значений;
  3. Поле, став вычисляемым, будет принимать значения во время выполнения события OnCalcFields так, как мы привыкли; он может не использоваться для целей фильтрации, в зависимости от реализации набора данных.

Тем не менее, определенный набор данных реализации может представлять некоторые другие последствия, в зависимости от его цели и возможностей.

Кто-нибудь заметил этот бит кода?

procedure TSampleForm.btnOpenClick(Sender: TObject);
begin
 if dsSample.Active then
   dsSample.Close;
 dsSample.SQL.text:='select id,state,oldstate,"" as txtState,"" as txtOldState from states where active=1';
 dsSample.Open;
end;

Он не меняет поле базы данных на вычисляемое поле... Он меняет несуществующее поле на вычисляемое поле. Он знает тип поля... это будет строка... Так что это большое дело... Нет... Это взломать... Да... Вы можете сделать то же самое в SQL с Приведение... На самом деле я никогда не видел причины для использования вычисляемых полей... Обычно я могу сделать то же самое проще в SQL.

Я добавил больше информации, немного покопавшись о том, почему бы и не сделать...

В коде sabri.arslan... для создания полей... из FieldList... также есть проблемы, связанные с отсутствием настройки ключей и обработки наследственных полей.

dmp:=DataSet.FieldDefs.Items[i].FieldClass.Create(self);

Тогда у нас есть Франсуа...

if you call the none hacked code you get this...
  for I := 0 to MyQuery.FieldDefList.Count - 1 do
    with MyQuery.FieldDefList[I] do
      if (DataType <> ftUnknown) and not (DataType in ObjectFieldTypes) and
        not ((faHiddenCol in Attributes) and not MyQuery.FIeldDefs.HiddenFields) then
        CreateField(Self, nil, MyQuery.FieldDefList.Strings[I]);

хм... отсутствует SetKeyFields, это приведет к непредвиденному поведению? Также, если ваше свойство ObjectView установлено в True... ваш набор данных не будет работать должным образом для иерархических полей... Похоже, безопаснее просто вызвать Hack для CreateFields, чем использовать его код... кроме того, что вы должны быть положительными что ваш компонент набора данных никогда не вызывает этот код...

CreateField вызывает CreateFieldComponent, и вы получаете Result:= FieldClassType.Create(Owner) для вашего TField

Взято из справки Borland по TFieldDef: "Определение поля имеет соответствующий объект TField, но не все объекты TField имеют соответствующее определение поля. Например, вычисляемые поля не имеют объектов определения поля".

Поэтому я спрашиваю вас... вы уверены, что не вводите неизвестное поведение, создавая Calculated Field на лету? Вы уверены, что Поля еще не были созданы или не будут созданы позже? (В коде sabri.arslan есть ошибка, потому что он выполняет открытие / после открытия... Он перезаписывает исходные поля TF..., я не понимаю, почему нам нужно воссоздавать TField для уже открытого набора данных)

Итак, что происходит, когда CreateFields вызывается набором данных (BDE и ADO делают это в InternalOpen и проверяют, чтобы убедиться, что они не имеют значений в полях... все ли компоненты набора данных делают это таким образом? Им не нужно). Что происходит с полями, которые вы уже создали... они перезаписаны? Я не видел никакого кода в TDataset или TFieldDef, который проверял, был ли TField уже создан для соответствующего TFieldDef, кроме проверки на DefaultFields(если у Fields есть значение).

TField либо сопоставляется со столбцом базы данных, либо не вычисляется. Это должно быть установлено во время разработки. Любая попытка изменить это во время выполнения привела к плохому дизайну IMO, и вы настраиваете себя на множество потенциальных головных болей.

Другие вопросы по тегам