【问题标题】:I need to avoid attempting to update non-physical fields in a Delphi TClientDataset connected to a TSQLQuery我需要避免尝试更新连接到 TSQLQuery 的 Delphi TClientDataset 中的非物理字段
【发布时间】:2012-11-10 10:23:49
【问题描述】:

Precis:我的代码正在尝试更新 Delphi XE TClientDataset 中的非物理字段(连接到带有 SQL 属性集的 TSQLQuery),这些字段是由运行时 Open 命令创建的.

我有一个 TClientDataset 连接到一个 TDatasetProvider 连接到一个 TSQLQuery 连接到一个 TSQLConnection。这些对象中的前 3 个被封装在一个库中的几个类中,我在多个项目的许多地方都使用了这些类。这些类在运行时创建这 3 个对象,并消除了大量重复代码,因为我有很多很多这样的三元组。

通常我会通过在TSQLQuerySQL 属性中指定一些SQL 并在TClientDataSet 上调用Open 从数据库中加载TClientDatasetTClientDataset 中的 Fields 是通过对 Open 的调用创建的,即。它们在Open 之前不存在。

我遇到了一个问题,其中TClientDataset 中生成的三个字段是非物理的;也就是说,SQL 进行计算以生成它们。不幸的是,在TClientDataset 中,这三个字段的创建与物理字段没有任何不同;他们的FieldKindfkData(理想情况下是fkInternalCalc),Calculated 属性是False(理想情况下是True)并且他们的ProviderFlags 包括pfInUpdate(理想情况下不应该)。毫不奇怪,当需要在 TClientDataset 上执行 ApplyUpdates 时,会引发异常...

Project XXX.exe raised exception class TDBXError with message
SQL State: 42S22, SQL Error Code: 207 Invalid column name 'Received'.
SQL State: 42S22, SQL Error Code: 207 Invalid column name 'Issued'.
SQL State: 42S22, SQL Error Code: 207 Invalid column name 'DisplayTime'.

我可以通过在TDatasetProviderOnUpdateData 事件处理程序中清除这些字段的pfInUpdate 标志来避免此错误。然而,这种解决方案要求位于上述通用类中的该函数知道特定的字段名称,从而破坏了代码的通用性。

我正在寻找一种将这些字段的计算性质发信号通知给事件处理函数的通用方法。

在调用Open 之后,我无法更改它们的FieldKindCalculated 属性(分别更改为fkInternalCalcTrue),因为这会生成WorkCDS: Cannot perform this operation on an open dataset 异常消息。而且,我无法在调用 Open 之前更改这些属性,因为 Fields 尚不存在。

我可以在Open 之后从这些FieldProviderFlags 属性中删除pfInUpdate 标志,但这不会传递到到达OnUpdateData 事件处理程序的“Delta”TClientDatset。我还尝试设置该字段的 FieldDefs.InternalCalcField 属性;再次,这不会传递给 Delta 数据集。

所以,我尝试过的所有信号想法都没有奏效。如果有任何新想法或替代方法,我将不胜感激。

我遇到的所有互联网搜索结果(包括 Cary Jensen 的优秀文章)都处理不适用于我的情况的设计时或非 SQL 生成的设置。

【问题讨论】:

  • 你的组件是从 TClientDataSet 派生的还是一个组合?
  • 希望我能理解您的问题。我提到的两个类本身不是组件,而是它们包含 TClientDataSet、TDataSetProvider 和 TSQLQuery 类,它们都不是派生的,即。未分类。在这两个类中,一个派生自另一个,但两者的基类仅派生自 TObject。
  • 你如何打开内部的ClientDataSet?我的意思是,你是调用类的方法还是直接调用内部的 ClientDataSet.Open 方法?
  • 内部TClientDataSet直接调用Open方法。 TClientDataSet 在我的派生类的构造函数中作为 var 参数返回,因为它在我的类外部的代码中广泛使用。
  • 我想到的更好的方法是将其更改为在您的类上调用 Open (或任何名称)方法,然后 Opens i> 内部 ClientDataSet。好消息是您不必更改所有代码库,而只需更改那些您想忽略 ApplyUpdates 更新中的某些字段的地方。

标签: windows delphi tclientdataset


【解决方案1】:

您可以在您的类中创建一种机制,为您希望在更新过程中忽略的各个字段预先配置 ProviderFlags。

根据你的问题,我建议你在类中创建一个新方法来打开内部 ClientDataSet,所有的魔法都将在这个方法中发生。

首先,一种简单的机制是包含一个新的 TStringList 属性,该属性列出了您要忽略的所有字段,您将按名称进行匹配。随意采用此方法或创建新的更好的机制,重要的是您能够确定要以这种方式配置的字段。

type
  TMyClass = class
    // all your current class here
  private
    FUpdateIgnoredFields: TStringList;
  public
    property UpdateIgnoredFields: TStringList read FUpdateIgnoredFields write SetUpdateIgnoredFields;
    //don't forget to create this in your constructor, free it in the destructor
    //and Assign any new value in the SetUpdateIgnoreFields method, as usual.
    procedure OpenInnerCDS; //the magic goes here
  end;

procedure TMyClass.OpenInnerCDS;
var
  FieldName: string;
  AFieldToIgnore: TField;
begin
  //opens the inner cds, but before that, configures the update-ignored  
  //fields in the underlying dataset
  //Let's call it InnerBaseDataSet;
  FInnerBaseDataSet.Open; //this opens the DataSet and creates all the fields for it.
  try
    for FieldName in FUpdateIgnoredFields do
    begin
      AFieldToIgnore := FInnerBaseDataSet.FindField(FieldName);
      if Assigned(AFieldToIgnore) then
        AFieldToIgnore.ProviderFlags := AFieldToIgnore.ProviderFlags - [pfInUpdate, pfInWhere];
    end;
    //now, let's open the ClientDataSet;
    FInnerClientDataSet.Open;
  finally
    //I suggest no matter what happens, always close the inner data set
    //but it depends on how the CDS->Provider->DataSet interaction is configured
    FInnerBaseDataSet.Close;
  end;
end;

//the way you use this is to replace the current ClientDataSetOpen with something like:

var
  MyInsance: TMyClass;
begin
  MyInstance := TMyInstance.Create();  //params
  try
    //configuration code here
    //MyInstance.InnerCDS.Open;  <-- not directly now
    MyInstance.UpdateIgnoreFields.Add('CALCULATED_SALARY');
    MyInstance.OpenInnerCDS;
    //use the CDS here.
    MyInstance.InnerCDS.ApplyUpdates(-1); //safely apply updates now.
  finally
    MyInstance.Free;
  end;
end;

把它当作一个想法。

我把所有的代码都写在这里了,可能语法有问题,但它显示了整个想法。

【讨论】:

  • 这个想法背后的原理是正确的,并且满足了我的信令需求。但是,正如我在之前的评论中提到的,异常发生在ApplyUpdates 期间,而不是在调用Open 时。要使用上述解决方案,我将在 TDataSetProvider 的 OnUpdateData 事件处理程序中使用您的 UpdateIgnoredFields 属性,因为 Open 对整个数据集进行操作,而 OnUpdateData 对“Delta”数据集进行操作。非常感谢您的帮助。对不起,我不能投票给你,我的“声誉”不足:-)
  • @Chris,所示方法只是防止在 ApplyUpdates 时出现异常。通过删除 pfInUpdate 标志。现在我想了更多,您还必须删除 pfInWhere 标志。试一试,你就会知道它是如何工作的。
  • 您也需要删除 pfInWhere 标志是正确的。但是,清除主 ClientDataSet 上的 ProviderFlags 不起作用——我在测试的早期尝试过这个没有成功——它只适用于“Delta”数据集。原因是,尽管您可以清除主数据集上的标志,但这不会向“Delta”数据集发出信号,该数据集仍然设置了标志。我试图通过 VCL 进行跟踪,但找不到“断开连接”,但它肯定不起作用。因此需要在仅处理增量数据集的 OnUpdateData 事件处理程序中执行此操作。
  • @Chris:我在字段存在于 ClientDataSet 之前执行此操作,因此当 ClientDataSet 在代码中打开几行后,更改必须传播.除非您在设计时创建它,否则它将起作用。
  • 啊。我根本不明白你的 FInnerBaseDataSet 是我的 TSQLQuery,而是我认为它是一个额外的 TClientDataset(我不清楚它的目的!)。因此,我已经实施了您的解决方案,因为它比我的解决方案更好;我的解决方案导致了更分散的代码和更复杂的使用。我所做的唯一更改是将字段作为开放数组传递,这样我就可以执行WorkQuery.SetFieldsExcludedFromUpdate(['DisplayTime', 'Issued', 'Received']) 后跟WorkQuery.Open 之类的操作,其中 WorkQuery 是我的类,它封装了 VCL 类链。再次感谢。
【解决方案2】:

您可以通过在 CDS 上设置相应的可选参数,将 ProviderFlags(以及一些其他属性)从客户端传递到提供者 (delta) 端。不要忘记设置 IncludeInDelta 参数

【讨论】:

    猜你喜欢
    • 2014-11-03
    • 2012-02-29
    • 2011-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多