Saturday, May 3, 2025

Delphi DOH! of the Day: Hardcoded Paths in TFDConnection.Params


I just spent two weeks tracking down an error that very easily, could have been avoided, if I hadn't been in such a rush.

I'm creating an FMX desktop application using Delphi 11.3 professional and a SQLite database which targets Windows and MacOS. I'm using a static class called TAppInfo which holds constants, variables, procedures, and functions for managing all the housekeeping bits for my application.

Inside the class constructor I'm using TPath.GetHomePath for managing paths:
//-----------------------------------------------------------------------  
// Build user paths 
//-----------------------------------------------------------------------  
ProgDataPath := IncludeTrailingPathDelimiter( TPath.GetHomePath) + VendorName + PathDelim + AppName;
Database     := ProgDataPath + PathDelim + DatabaseName;
This works great. It allows me to test both Windows and MacOS. In addition, I can easily test this on non-development machines by making sure I put the SQLite database file in the corresponding Application Path. It lets me simulate how everything would work if the application had been installed using an installer.

Everything had been going great. 
Build a little code. 
Test on Windows (my development machine). 
Test on MacOS using PAServer. 
Test on a non-development Windows machine using a thumb drive. 
All is good.

And then BOOM! 

It stopped working on the non-development test machine.

I kept getting a FireDAC SQLite error.


I couldn't figure out why all of the sudden it just crashed when I tried running it on a test machine. I spent hours creating MAP files, and testing, and asking ChatGPT to help me troubleshoot. 

I wrapped exception logic inside OnCreate event handlers. 
Nothing. 
Same error without displaying the exception logic.

"I may not be the brightest bulb on the tree..."


I may not be the brightest bulb on the tree but I knew something was different. I just couldn't figure it out or remember what I did that was different. By now two weeks has gone by and no resolution.

This morning I stripped out all the exception code and went back to square one. I looked at the project source. The only three units listed are two data modules and the main program. 

begin
  Application.Initialize;
  Application.CreateForm(TdmImages, dmImages);
  Application.CreateForm(TdmSQLite, dmSQLite);
  Application.CreateForm(TfMain, fMain);
  Application.Run;
end.
  • There's nothing SQLite related in the dmImages data module.
  • It's not getting to fMain.
  • It must be dmSQLite.
It's doesn't make sense. I've got exception logic inside the OnCreate event handler of dmSQLite but it's not firing off.

And then I had an A-Ha! moment. Right click on the dmSQLite data module and view as text!

Then I remembered what happened!

And there it was. The TFDConnection had hardcoded Params.Strings with the Database configured to a file located on my development machine and the DriverID = to SQLite.

Then I remembered what happened. I had been creating a new FastReport within the IDE. The report needed data from the SQLite database. So, I configured the TFDConnection to point to the SQLite database so I could drag and drop the database columns onto the report from within the IDE. 

Everything worked great! The report was spot on! I spent three days tweaking the report until it looked just right. I couldn't wait to test it on the non-development machine.

Ooops! I was in such a rush, I forgot to remove the hardcoded Params.Strings

I asked ChatGPT for an explanation as to why the OnCreate exception logic didn't fire. 

Explanation from ChatGPT:
  • Delphi loads the .dfm or .fmx file and instantiates all the components declared within it. 
  • This includes setting all published properties like FDConnection.Params, FDQuery1.Connection, Button1.text, etc.
  • This happens before the OnCreate event fires.
  • After all components are created and their published properties are set, OnCreate is triggered.

🛠 Your Specific Case

If TFDConnection.Params had hardcoded values (e.g. a file path or server name that doesn't exist on the test machine), they were already applied before OnCreate — meaning the connection could already try to auto-connect or be used by another component, causing a crash.

Lessons Learnt:
  1. 80 miles an hour is faster than 100 miles an hour. When you rush you make mistakes.
  2. Published Properties inside .dfm and .fmx files are set before any corresponding OnCreate event handlers.
  3. Make it a priority to View forms and data modules as text when troubleshooting.
  4. Remember Occams Razor: "The simplest explanation is usually the best one."
Enjoy,
Semper Fi
Gunny Mike
https://zilchworks.com

10 comments:

  1. Problem can be solved just do this:
    1. AutoConnect should be false, you can set this onCreate if you need it.
    2. Connected should be false, always connect after you setup your connection properties.

    ReplyDelete
    Replies
    1. This is a big mistake of Delphi connection component. The Active/Conneccted property must has "stored False" to avoid persisted and make connection before you have a chance to configure it

      Delete
  2. This used to get me, too, until I discovered a really nice feature of TFDConnection: The ConnectedStorageUsage property. Expand this in the Object Inspector and you'll see two checkbox options: auDesignTime and auRuntime.

    Yes, they do what you might expect: with auDesignTime checked and auRuntime UNCHECKED, you can leave the Connected property active so you can see your data at design time and not worry about a dataset trying to go live before OnCreate because you left it connected! This property is also in each dataset component (TFDTable, TFDQuery, etc.), so remember to set those as well.

    ReplyDelete
    Replies
    1. 💯 Wow! So you really can, "Have your cake and eat it too!". Thank you David, this is brilliant +♾

      Delete
  3. For what it's worth, I try and make sure I don't connect things up visually - it sort of goes against the Delphi RAD approach, but I've learnt over many years that it's much better to have explicit code lines that allow me to control how and when components interact with each other.

    ReplyDelete
    Replies
    1. Shane, this does go against the RAD approach. I know that Delphi has always been associated with "database development". I however, have avoided trying to make my Delphi applications work with databases until last year.

      Why? Because until last year my attitude was "I need to force Delphi to work the way I think." It only took 35+ years of working with Turbo Pascal and Delphi to realize, "perhaps, I was the one who needed to change". If I hadn't been so stubborn I probably would have discovered some of these database nuances.

      Delete
  4. Don't visually add a non visual component. Don't follow this stupid delphi approach.

    ReplyDelete
    Replies
    1. I like David's approach of keeping auDesignTime checked and keeping auRunTime unchecked. This gives you the best of both worlds.

      Delete
  5. Bugs like these have bitten me like 25 years ago, so I learned not to do it. Apparently I wasn't alone, because around the year 2000 GExperts added the "Set Component Properties" expert which comes with defaults for setting the following properties to False when saving a form:
    * TAdoConnection.Connected
    * TDatabase.Connected
    * TDataSet.Active

    ( https://svn.code.sf.net/p/gexperts/code/trunk/webhelp/index.html?setcomponentproperties.html )

    ReplyDelete
    Replies
    1. Thanks for sharing. I've been using Delphi since it was Turbo Pascal 3. I know Delphi is KNOWN for being a database application development tool. I use SQL Server everyday at my day job. I stayed away from doing database application development with Delphi because... wait for it... "I tried to make Delphi work the way I think."

      It finaly dawned on me, after 35 years, that perhaps I was the one who needed to change my thinking instead of trying to twist Delphi into a "Riley shaped pretzel". Talk about DOH!

      Delete