TFDMemTable shows stale rows after external edits to a joined TFDQuery's source tables
0 reputation · 18 May 2021, 02:47 UTC
0 reputation · 18 May 2021, 02:47 UTC
Goal: establish when FireDAC invalidates the in-memory buffer of a TFDMemTable populated from a TFDQuery whose SELECT joins several tables, in a Delphi 12 multi-user application. Exact refresh semantics are assumed similar across recent FireDAC releases, but they need checking against the installed version.
TFDMemTable is a disconnected in-memory dataset, so INSERT, UPDATE or DELETE activity from other sessions is invisible to it until something re-reads the source. FireDAC documents Refresh and UpdateOptions.RefreshMode for re-reading posted rows, but neither is clearly specified for rows changed externally between fetches, particularly with joined queries. CachedUpdates on the source query adds an unapplied delta that any reload must coexist with.
The unresolved point is which mechanism is the supported refresh path for this setup, and what each option costs:
TFDQuery.Refresh re-execute a joined SELECT, and when the MemTable was filled via CopyDataSet, is a second copy the only way new rows appear?CachedUpdates deltas and the current cursor position survive a reload without lost or duplicated edits?35275 reputation · 18 May 2021, 05:09 UTC
The short answer: nothing invalidates the MemTable, ever. A TFDMemTable filled via CopyDataSet is a disconnected snapshot with no live link to the database, so external INSERT/UPDATE/DELETE activity is invisible to it by design. The supported refresh path is: re-fetch on the source TFDQuery (which re-executes the entire joined SELECT), then copy into the MemTable again. There is no generic push notification in FireDAC; scheduled or on-demand re-fetching is the expected design. Everything below separates what is well-established from what you should verify against your installed FireDAC build.
TFDQuery.Refresh re-executes the query's SQL — for a joined SELECT, the whole join runs again — but it only refreshes the query's own buffer. The MemTable does not change until you call CopyDataSet (or Assign) a second time.UpdateOptions.RefreshMode and RefreshRecord re-read a row after your own Post, to pick up trigger results or defaults. They do not detect or merge changes made by other sessions between fetches.TFDEventAlerter exists for some drivers (e.g., Oracle), but it delivers only a "something changed" signal — you still re-query yourself, and availability is database-specific.CachedUpdates active on the source query, call ApplyUpdates (or CancelUpdates if the edits are disposable) before re-fetching. What Refresh does to an unapplied delta is option- and version-sensitive — do not rely on it silently surviving.CopyDataSet replaces its content wholesale and local edits are lost.FDQuery1.Refresh (or Close/Open), then FDMemTable1.CopyDataSet(FDQuery1) again.Locate on the saved key.FDQuery1.ApplyUpdates; // or CancelUpdates
SavedKey := FDMemTable1.FieldByName('ID').AsInteger;
FDQuery1.Refresh; // re-runs the joined SELECT
FDMemTable1.CopyDataSet(FDQuery1);
FDMemTable1.Locate('ID', SavedKey, []);TFDMoniFlatFileClientLink) and confirm Refresh actually re-executes the joined SELECT in your build.Re-copying a large joined result on a timer is expensive. If volume matters, narrow the WHERE clause, fetch only changed keys and merge manually, or move the "live" view off the MemTable entirely. One detail worth confirming before finalizing the design: whether your MemTable holds user edits of its own — if so, a merge-by-key strategy replaces step 3, and the reload logic changes substantially.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.