You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何检测TDataSet中字段的变更及查看变更前后值?

检测TDataSet字段变更的实现方案

针对你提出的两个需求(识别变更字段、获取前后值),这里提供两种实用的实现方式,适配不同的DataSet使用场景:

一、利用FireDAC的OnUpdateRecord事件(推荐FireDAC用户)

如果你的DataSet是FireDAC组件(如TFDQuery、TFDTable),且开启了缓存更新(CachedUpdates = True),OnUpdateRecord是最直接的方案——FireDAC会自动维护字段的新旧值,无需手动缓存。

实现步骤:

  1. 为DataSet绑定OnUpdateRecord事件;
  2. 在事件中遍历字段,通过Field.Modified判断是否变更,再通过Field.OldValue和Field.NewValue获取前后值;
  3. 根据ARequest参数区分更新、插入、删除三种操作类型。

代码示例:

procedure TYourForm.YourDataSetUpdateRecord(ASender: TDataSet;
  ARequest: TFDUpdateRequest; var AAction: TFDErrorAction;
  AOptions: TFDUpdateRowOptions);
var
  I: Integer;
  Field: TField;
  OldVal, NewVal: Variant;
begin
  case ARequest of
    arUpdate:
      begin
        // 遍历所有字段,检查变更
        for I := 0 to ASender.FieldCount - 1 do
        begin
          Field := ASender.Fields[I];
          if Field.Modified then
          begin
            OldVal := Field.OldValue;
            NewVal := Field.NewValue;
            // 这里替换为你的业务逻辑(如记录日志、验证等)
            OutputDebugString(PChar(Format('字段[%s]变更:%s => %s', 
              [Field.FieldName, VarToStr(OldVal), VarToStr(NewVal)])));
          end;
        end;
      end;
    arInsert:
      begin
        // 插入操作:所有字段为新值,无旧值
        for I := 0 to ASender.FieldCount - 1 do
        begin
          Field := ASender.Fields[I];
          OutputDebugString(PChar(Format('插入字段[%s]:%s', 
            [Field.FieldName, VarToStr(Field.NewValue)])));
        end;
      end;
    arDelete:
      begin
        // 删除操作:仅保留旧值
        for I := 0 to ASender.FieldCount - 1 do
        begin
          Field := ASender.Fields[I];
          OutputDebugString(PChar(Format('删除字段[%s]:%s', 
            [Field.FieldName, VarToStr(Field.OldValue)])));
        end;
      end;
  end;

  // 允许更新继续执行,若需拦截可设为eaAbort
  AAction := eaApplied;
end;

注意事项:

  • 必须确保CachedUpdates = True,否则OnUpdateRecord不会触发;
  • Field.OldValue仅在缓存更新模式下有效,直接更新模式(CachedUpdates = False)无法获取旧值。

二、手动缓存旧值(适配所有TDataSet子类)

如果使用非FireDAC组件(如TADODataSet、TClientDataSet),或不想开启缓存更新,可以在编辑/插入前缓存旧值,再在提交前对比新旧值。

实现步骤:

  1. 定义一个字典变量存储旧值;
  2. 在BeforeEdit事件中缓存当前字段值,BeforeInsert事件中初始化空字典;
  3. 在BeforePost事件中遍历字段,对比缓存的旧值与当前值,识别变更;
  4. 在AfterCancel事件中释放缓存字典,避免内存泄漏。

代码示例:

type
  TYourForm = class(TForm)
    YourDataSet: TADODataSet;
    procedure YourDataSetBeforeEdit(DataSet: TDataSet);
    procedure YourDataSetBeforeInsert(DataSet: TDataSet);
    procedure YourDataSetBeforePost(DataSet: TDataSet);
    procedure YourDataSetAfterCancel(DataSet: TDataSet);
  private
    FOldValues: TDictionary<string, Variant>;
  end;

procedure TYourForm.YourDataSetBeforeEdit(DataSet: TDataSet);
var
  I: Integer;
  Field: TField;
begin
  FOldValues := TDictionary<string, Variant>.Create;
  // 缓存所有字段的当前值(即旧值)
  for I := 0 to DataSet.FieldCount - 1 do
  begin
    Field := DataSet.Fields[I];
    FOldValues.Add(Field.FieldName, Field.Value);
  end;
end;

procedure TYourForm.YourDataSetBeforeInsert(DataSet: TDataSet);
begin
  // 插入操作无旧值,初始化空字典
  FOldValues := TDictionary<string, Variant>.Create;
end;

procedure TYourForm.YourDataSetBeforePost(DataSet: TDataSet);
var
  I: Integer;
  Field: TField;
  OldVal, NewVal: Variant;
begin
  try
    for I := 0 to DataSet.FieldCount - 1 do
    begin
      Field := DataSet.Fields[I];
      if FOldValues.TryGetValue(Field.FieldName, OldVal) then
      begin
        NewVal := Field.Value;
        // 对比新旧值(注意Variant类型的相等判断)
        if not VarEquals(OldVal, NewVal) then
        begin
          OutputDebugString(PChar(Format('字段[%s]变更:%s => %s', 
            [Field.FieldName, VarToStr(OldVal), VarToStr(NewVal)])));
        end;
      end
      else if DataSet.State = dsInsert then
      begin
        // 插入操作输出新值
        OutputDebugString(PChar(Format('插入字段[%s]:%s', 
          [Field.FieldName, VarToStr(Field.Value)])));
      end;
    end;
  finally
    // 释放缓存字典
    FOldValues.Free;
  end;
end;

procedure TYourForm.YourDataSetAfterCancel(DataSet: TDataSet);
begin
  // 取消编辑时释放缓存,避免内存泄漏
  if Assigned(FOldValues) then
    FOldValues.Free;
end;

注意事项:

  • VarEquals用于判断Variant类型值的相等,避免因类型差异导致误判;
  • 若DataSet有大量字段,可优化逻辑,只缓存主键或需要监控的字段,提升性能。

内容的提问来源于stack exchange,提问作者Michael Hutter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 22:08:14