FDQuery设fmOnDemand时Locate全量拉取,求更优雅解决方案
FDQuery fmOnDemand模式下Locate避免拉全量数据的优雅方案
问题根源
当FDQuery.FetchOptions.Mode设为fmOnDemand时,Locate/LocateEx会触发FireDAC自动拉取所有未加载的数据——因为Locate需要全局遍历数据集匹配目标项,而fmOnDemand的按需加载逻辑会被这个全局查找需求打破。切换到fmManual模式后,FireDAC不会自动拉取数据,Locate仅在已加载到本地的数据集中查找,从而避免全量拉取。
方案1:用BeforeLocate/AfterLocate事件自动切换模式
无需修改现有数据集类,通过绑定事件封装模式切换逻辑,同时加入线程同步适配异步场景:
procedure TYourForm.FDQueryBeforeLocate(DataSet: TDataSet; var KeyFields: string; KeyValues: Variant; Options: TLocateOptions; var Accept: Boolean); var LQuery: TFDQuery; begin if DataSet is TFDQuery then begin LQuery := TFDQuery(DataSet); // 用Tag临时保存原模式,加同步防止异步线程冲突 TMonitor.Enter(LQuery); try LQuery.Tag := Integer(LQuery.FetchOptions.Mode); LQuery.FetchOptions.Mode := fmManual; finally TMonitor.Exit(LQuery); end; end; end; procedure TYourForm.FDQueryAfterLocate(DataSet: TDataSet; Found: Boolean); var LQuery: TFDQuery; begin if DataSet is TFDQuery then begin LQuery := TFDQuery(DataSet); TMonitor.Enter(LQuery); try LQuery.FetchOptions.Mode := TFetchMode(LQuery.Tag); finally TMonitor.Exit(LQuery); end; end; end;
优点:无需替换现有FDQuery实例,直接绑定事件即可生效,适合已有项目快速改造。
方案2:重写TFDQuery封装Locate逻辑
如果希望代码更封装,可自定义TFDQuery子类,将模式切换逻辑内置到Locate方法中:
type TMyFDQuery = class(TFDQuery) public function Locate(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions): Boolean; override; function LocateEx(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions; const Filter: string = ''): Boolean; override; end; function TMyFDQuery.Locate(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions): Boolean; var LOriginalMode: TFetchMode; begin // 线程同步保护,适配异步场景 TMonitor.Enter(Self); try LOriginalMode := FetchOptions.Mode; FetchOptions.Mode := fmManual; try Result := inherited Locate(KeyFields, KeyValues, Options); finally FetchOptions.Mode := LOriginalMode; end; finally TMonitor.Exit(Self); end; end; function TMyFDQuery.LocateEx(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions; const Filter: string): Boolean; var LOriginalMode: TFetchMode; begin TMonitor.Enter(Self); try LOriginalMode := FetchOptions.Mode; FetchOptions.Mode := fmManual; try Result := inherited LocateEx(KeyFields, KeyValues, Options, Filter); finally FetchOptions.Mode := LOriginalMode; end; finally TMonitor.Exit(Self); end; end;
优点:调用方无需关注模式切换逻辑,直接使用自定义类即可,代码更简洁可维护。
异步场景适配注意事项
- 线程同步:无论用事件还是重写方法,必须用
TMonitor或TCriticalSection保护模式切换逻辑,防止多线程同时修改FetchOptions.Mode导致的状态混乱。 - 数据集隔离:异步线程尽量使用独立的FDQuery实例,避免跨线程操作同一个UI线程的数据集,减少同步冲突。
- 避免嵌套操作:不要在Locate的事件回调(如AfterScroll)中触发其他数据集操作,防止嵌套同步导致死锁。
内容的提问来源于stack exchange,提问作者BxOO
相关产品推荐
相关产品推荐

