4

正如问题的标题一样,我与一位同事争论应该如何使用计算字段。据我所知,计算字段是在运行时创建的,就像弗朗索瓦对在运行时将计算字段添加到查询中的问题的回答一样。在同一个问题上,还有另一个答案,来自 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:这只是“不要这样”的一个例子。作为一个论点,我问这样做之后讨论中该领域的行为是什么。该领域将如何运作?

4

3 回答 3

3

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

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

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

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

但是,根据其目的和功能,某个数据集实现可能会产生一些其他后果。

于 2013-08-13T14:55:08.773 回答
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;

他没有将数据库字段更改为计算字段...他将不存在的字段更改为计算字段。他知道字段类型...它将是一个字符串...所以这有什么大不了...不...这是一个 hack...是的...您可以在 SQL 中做同样的事情演员...事实上,我从来没有真正看到使用计算字段的理由...我通常可以在 SQL 中更轻松地做同样的事情。

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

sabri.arslan 代码...从 FieldList 创建字段...也存在缺少设置键和处理 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 获得 Result := FieldClassType.Create(Owner)

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

所以我问你......你确定你没有通过动态创建计算字段来引入未知行为吗?您确定字段尚未创建或以后不会创建?(sabri.arslan 代码中有一个错误,因为他打开/打开后...他覆盖了原始 TFields...,我不明白为什么我们需要为已经打开的数据集重新创建 TFields)

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

于 2013-08-13T19:16:20.467 回答
0

TField 要么映射到数据库列,要么不映射,而是通过计算得出。这应该在设计时设置。任何在运行时改变这一点的尝试都带有糟糕的设计 IMO 的味道,而且你正在为很多潜在的头痛做好准备。

于 2013-08-13T10:13:48.300 回答