Delphi - 在运行时将字段更改为计算字段。这是一个好习惯吗?

时间:2013-08-13 10:07:19

标签: database delphi calculated-field tdataset

在问题标题中,我与一位同事争论如何使用计算字段。 据我所知,计算字段是在运行时创建的,就像问题answer中的FrançoisAdding a calculated field to a Query at run time一样。在同一个问题上,还有另一个来自sabri.arslan的answer,建议将现有字段更改为计算字段(代码如下)

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的未知行为。在运行时将数据集字段更改为计算字段是安全的吗?这会产生什么样的问题?

LE:这是一个问题。其目的是演示在运行时在数据集上添加计算字段的良好实践。并且,是的,在运行时添加计算字段是糟糕的设计。

LE2:这只是“不要这样做”的一个例子。作为一个论点,我问在这样做之后讨论领域的行为是什么。该领域将如何行动?

3 个答案:

答案 0 :(得分:3)

不,这不是一个好习惯。代码复杂的简单事实表明应该避免这种做法。有人已经提到了KISS原则,我同意这一点。

特别是,数据集必须打开两次的简单事实足以让我不喜欢这种做法。

此外,将字段的性质从数据更改为计算将改变数据集在其内部记录表示中组织字段的方式(数据集调用的内容) 记录缓冲区)。这种表示可能与一个数据集实现到另一个数据集实现非常不同。由于该问题未确定特定数据集,因此行为的变化(通常):

  1. 数据字段将其值存储在属于底层数据库客户端的结构中;计算字段将其值存储在非持久缓冲区中;
  2. 在数据集打开期间,有一个名为字段绑定的进程,用于将数据字段绑定到数据库客户端对应的结构;当此绑定失败时,数据集通常会引发异常; 计算字段不参与此过程,因为它们使用内部字段缓冲区来存储其值;
  3. 该字段在成为计算字段后,将按照我们习惯的方式在 OnCalcFields 事件执行期间接受值;它可能不会用于过滤目的,具体取决于数据集的实现。
  4. 然而,根据其目的和特征,某个数据集的实现会产生一些其他后果。

答案 1 :(得分:1)

有没有人注意到这段代码?

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中做同样的事情Cast ...事实上我从来没有真正看到使用计算字段的理由......我通常可以在SQL中更容易地做同样的事情。

我在更多地挖掘了为什么不这样做之后添加了更多的信息.​​..

sabri.arslan代码...从FieldList创建Fields ...也存在缺少设置密钥和处理heirchy字段的问题。

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,你得到你的TField的结果:= FieldClassType.Create(Owner)

取自TFieldDef的Borland帮助 “字段定义具有相应的TField对象,但并非所有TField对象都具有相应的字段定义。例如,计算字段没有字段定义对象。”

所以我问你......你是否积极通过动态创建CalculatedField来引入未知行为? 您是否肯定尚未创建字段或稍后不会创建字段? (sabri.arslan代码中有一个错误,因为他打开/打开后......他正在覆盖原来的TFields ......,我不明白为什么我们需要为已经打开的数据集重新创建TField)

那么当数据集调用CreateFields时会发生什么(BDE和ADO在InternalOpen上执行此操作 并检查以确保它们在字段中没有值...所有数据集组件都执行此操作 办法?他们不必)。 您已经创建的字段会发生什么......是否会被覆盖?我没有看到任何代码 在TDataset或TFieldDef中检查是否已经为相应的TFieldDef创建了TField而不是 检查DefaultFields(如果Fields有值)。

答案 2 :(得分:0)

TField要么映射到数据库列,要么不映射到数据库列,并且是通过计算得出的。这应该在设计时设置。任何试图在运行时改变这种情况的尝试都会让IMO设计不好,而且你正在为自己设置很多潜在的麻烦。