multithreadingdatabase-connectionc++builderfiredactthread

I'm having trouble creating a generic Thread Class for connecting to different databases defined at run-time using C++ Builder 11.3


The databases are created at run-time. The thread should be run every 15 minutes to query for new records in a given database. I've gotten it the point where the Thread seems to be working, but the TFDConnection keeps throwing a:

Thread: [FireDAC][Comp][Clnt]-512. Connection is not defined for []. Possible reason: Connection and ConnectionName property values are both empty

I tried to follow the Delphi example for Pooling, but Delphi doesn't exactly port well to C++. So I did it the best way I know. Here's are two code snippets for the Thread and the calling method.

__fastcall TDataHandlerThread::TDataHandlerThread(bool CreateSuspended, TFDPhysDriverLink *ADriverLink, 
                                                  TDataSource *DataSource, String AConnectionStr, 
                                                  String ACommandStr) : TThread(false)
{

    FreeOnTerminate = true;

    try {
        std::unique_ptr<TFDConnection>   FConnection    = std::make_unique<TFDConnection>(nullptr);
        std::unique_ptr<TFDEventAlerter> FAlerter       = std::make_unique<TFDEventAlerter>(nullptr);
                                         FQuery         = std::make_unique<TFDQuery>(nullptr);  
        FDataSource = DataSource;
    
        TFDPhysDriverLink   *DriverLink = ADriverLink;
        
        FAlerter->OnAlert = PgEventAlerterAlert;

        FAlerter->Options->Kind = "Notifies";
        FAlerter->Options->Synchronize = True;
        FAlerter->Options->Timeout = 10000;
        FAlerter->OnAlert = PgEventAlerterAlert;
        FAlerter->Active = True;
        FAlerter->Register();
        
        
        FConnection->ConnectionString = AConnectionStr;   // Prvate Connection String;
        FConnection->ConnectionName="PG";
        FConnection->Connected = true;

        FQuery->Connection = FConnection.get();
        FQuery->SQL->Text = ACommandStr;
        FDataSource->DataSet = FQuery.get();


    } catch (const Exception &exception) {
        AlertMainForm("Exception in Thread: " + exception.Message); 
        Terminate();
    }
}
//---------------------------------------------------------------------------

void __fastcall TDataHandlerThread::AlertMainForm(String str)
{
    TThread::Queue(NULL, [this, str]() -> void {    
        MainForm->Memo->Lines->Add("THREAD: " + str);
    });
}

TDataHandlerThread::~TDataHandlerThread()
{

}
//---------------------------------------------------------------------------

void __fastcall TDataHandlerThread::Execute()
{
        try
        {
//          while(!Terminated)
            if(!Terminated)
            {
                if(FQuery->Active) {
                    FQuery->Close();
                    FDataSource->Enabled=false;
                }

                FQuery->Open();
                FDataSource->Enabled=true;

                std::this_thread::sleep_for(std::chrono::minutes(5));           
            }
        }
        catch(const Exception &exception)
        {
            AlertMainForm("THREAD: ERROR " + exception.Message);
        }
}

And from the calling method:

void __fastcall TScheduler::DoExecute()
{
    try
    {
        String Pg_Connect_str  = "DriverID=PG;Server=XX.XXX.XX.XXX;Port=XXXXX;Database=XXXXX;User_Name=XXXXXX; \
                                    Password=----;CharacterSet=utf8;ExtendedMetadata=true;";

        String CmdStr = "select * from customers WHERE last_update_time > " + FormatDateTime( "yyyy-mm-dd hh:mm:ss", FLastRetrievalTime ) + "'");;

        TDataHandlerThread *runner = new TDataHandlerThread(false, FDPhysPgDriverLink, dsJwareCustomers, Pg_Connect_str,  CmdStr);

    } catch (const Exception &exception) {
        MainForm->ExceptionThrown("Exc:[0x0A][Execute]:" + exception.Message );
    }
}

In the past, I've been able to drop a TFDConnection onto a DataModule for use with Postgres and set it up entirely in code, not assigning a ConnectionName nor a Driver, yet using the same technique here it is not getting the connection.


Solution

  • You are creating and destroying the TFDConnection object (and the TFDEventAlerter object) in your thread's constructor using local unique_ptr variables that go out of scope when the constructor exits.

    Thus, the TFDConnection object won't exist anymore when the thread calls Execute() , so the TFDQuery object can't do its work. You can verify this by looking at the FQuery->Connection property inside of Execute() - it will have been reset back to null when the TFDConnection object is destroyed.

    If you are going to create the objects in the thread's constructor, you need to get rid of the local unique_ptr variables. Make them members of the thread class instead, as you appear to have done for the TFDQuery object.

    Otherwise, create all objects locally in Execute() instead.