FireDAC BatchMove and Calculated Fields

If you need to “pump” records from one data source to an other, then TFDBatchmove is for you.

Using FireDAC’s BatchMove component allows you to easily map the fields of one query to an other, regardless if they belong to the same database/connection or two totally different databases. You can actually even copy records from text files to “real” databases and vice versa.

Just build a BatchMove chain like this:

SourceData and DataTarget are regular TFDQueries, Reader and Writer are TFDBatchmoveDatasetReader/Writer components, that just connect the queries with the TFDBatchmove component in the middle.

The actual magic is done by adding field mappings, from source to target:

In this case source and target fieldnames are mostly the same, which wouldn’t even require adding mappings. There are a couple of calculated fields in the source query too. They are used to format/modify the data, so that it would match the format requirements in the destiantion query.

These fields are calculated in QOPCQuery.OnCalcFields like this:

procedure TDMMain.QOPCSourceDataCalcFields(DataSet: TDataSet);
begin
  if QOPCSourceDataoSchnittgruppe.IsNull then
    DataSet.FieldByName('FANr').AsString := ''
  else
    DataSet.FieldByName('FANr').AsString := GenFANr(QOPCSourceDataoSchnittgruppe.AsInteger); 

The calculated fields work fine, but when calling BatchMove.execute it will raise an exception. The reason is simple:

FireDAC’s internal logic depends on TDataSet.FieldDefs, but calclulated fields won’t show up there. You have to use “InternalCalc” instead of “Calculated”, to push your calculated fields to its datataset’s FieldDefs collection.

Firemonkey TMemo Scrolling

When you add lines to a TMemo in Delphi Applications, then you might want to scroll to the very end of that particular Memo, so that the newest lines are kept in view.

In FireMonkey it’s as easy as just calling GoToTextEnd:

procedure TFormMain.AddLog(AMessage: string);
begin
  Memo1.Lines.Add(AMessage);
  Memo1.GoToTextEnd;
end;

You might notice though, that when you clear the Memo at certain times, the scrollbars may get out of sync. To avoid that, you need to call ProcessMessages right after “clear”. Apparently the scrollbars get confused, when you add lines directly after clear command, because the actual position needs to be rendered first (which the ProcessMessages does).

procedure TFormMain.AddLog(AMessage: string);
begin
  if Memo1.Lines.Count > 100 then 
    begin
      Memo1.Lines.Clear;
      Application.ProcessMessages; //without this, scrollbars will show wrong size/position
    end;
  Memo1.Lines.Add(AMessage);
  Memo1.GoToTextEnd;
end;

MVVM Anwendungen mit Delphi und nicht gegen Delphi

Im folgenden Video meines Youtube-Kanals eine Einführung zum Thema MVVM Anwendungen mit Delphi. Wichtige Motivation ist für mich, dass ich soviel Delphi wie möglich verwende, also eben nicht anfange, alles von Hand im Source-Editor hinzuschreiben.

Hinweis: Das Video versteht sich als erste Einführung in das Thema MVVM mit Delphi. Selbstverständlich gibt es noch eine ganze Reihe von Dingen, die man optimieren und ausbauen kann. Weitere Schritte werde ich hier in noch folgenden Beiträgen skizzieren.

Das Video ist in Deutsch.

TTask / TThreadPool may keep interfaced objects alive

I am using TTask.Run quite a lot in projects and in many of my demos. I won’t go into the details of how to use TTask here, but I only want to highlight that there is still an open issue, which may cause interfaced objects to be destroyed much later, than you would expect. Ultimately this may lead to a much higher memory footprint of your application.

As of Delphi 10.1 Update 2 the issue is still marked open on quality.emabaracdero.com – you might vote for it, if your code is affected by it.

Interfaced objects use automatic reference counting for lifecycle management:

var
  LFoo : IFoo
begin
  LFoo := TFoo.Create;
  //work with LFoo
end;  //LFoo goes out of scope, its reference counter will go to zero,
      // thus it will be destroyed here automatically.

With TTask.Run you can start background threads, that will execute code passed in as anonymous method:

var
  LFoo : IFoo
begin
  LFoo := TFoo.Create;
  TTask.Run(procedure
    begin
      Foobar(LFoo); //Work with LFoo 
    end;
  ).Waitfor; //Wait for task/thread to be completed 
end;  //LFoo goes out of scope, its reference counter SHOULD go to zero,
      // thus it SHOULD be destroyed here automatically.   

Unfortunately the above does not work as expected. The internals of TTask (actually TThreadpool) keep a reference to the (already finished) task and prevent the instance of TFoo, which is referenced by LFoo, to be destroyed automatically. At least when the TThreadPool gets destroyed, all those kept references will be released and the instances will be destroyed, thus they won’t report as memory leak (with ReportMemoryLeaksOnShutDown).

In other words: you will have to set LFoo to nil manually – until this issue gets solved in one of the next updates.

  TTask.Run(procedure
    begin
      Foobar(LFoo); //Work with LFoo 
    end;
  ).Waitfor; //Wait for task/thread to be completed 
  LFoo := nil; //Depending on your logic, this could also be done inside TTask.Run
end;    

Sharing DataModules between VCL and FMX projects

When you start implementing cross-platform applications in Delphi’s FireMonkey framework, then you might consider reusing TDataMdoule instances from your existing VCL applications. If your code is clean enough, i.e. your DataModules are separated from any UI code, that is contain no references to WinApi, Forms, Controls or other UI relevant units, then this is actually a reasonable approach.

You might run into a weird problem though: The IDE will sometimes insist on adding VCL-dependant units to your DataModul, even if you actually compile for FireMonkey. One example is FireDAC.VCL.Wait which will get added automatically under certain conditions (and which won’t compile under FMX).

There is a property that controls this behaviour in shared TDataModule instances:

ClassGroup is basically a “Pseudo Property”, which adds some magic to the project so that the IDE will behave differently depending on the properties value. To make your DataModule really platform independent set it to:   System.Classes.TPersistent

That way, the IDE stops inserting references to either FMX or VCL units in the DataModule’s uses clause. In the case of FireDAC applications, it will just add FireDAC.ConsoleUI.Wait, which will compile just fine on all platforms. You would then add the actual Wait unit in the app’s main form (or in the DPR).