Issue with TFDQuery on a form connecting to a datamodule - delphi

I have a datamodule with a TFDConnection connecting to a SQLLite db.
Queries on the datamodule work fine. But if I have a query on a form connecting to the connection on the datamodule when setting Active to true I get the error:
exception message : [FireDAC][Comp][Clnt]-512. Connection is not
defined for [FDQuery1]. Possible reason: Connection and ConnectionName
property values
This happens in design time.
This is with Delphi Tokyo in a Firemonkey mobile app.

I think this may be a FireDAC (or IDE) bug which has been introduced between Delphi Seattle an Tokyo (10.2). I found I could reproduce it as follows:
Create a new multi-device (FMX) project in Seattle.
Add a datamodule to the project and add an FDConnection to it. I configured the FDConnection to use the MSSQL driver, set the connection to use OS Authentication, my local Sql Server and an existing db on it. I set LoginPrompt to false.
I added FDQuery1 to the form, made the form USE the datamodule's unit, then set FDQuery1's connection to the one on the datamodule and added "select * from mytable" as its Sql. Then I set FDQuery1.Active to true in the OI. FDQuery1 opened without complaint. I reset FDQuery1.Active to false, saved the project and closed it.
I closed Seattle, started Tokyo and opened the project. When I set FDQuery1.Active to true, I got the exact same exception message as you have reported. Interestingly, the OI then updates FDQuery1.Active to display True.
I then set FDQuery1.Active to false then true, and the exception did not occur.
I closed and restarted Tokyo, re-opened the project and, again, the first time (but only the first time) I set FDQuery1.Active to true, the exception occurs again.
You are welcome to include the above steps in a problem report to Emba. Btw, I didn't spend any time investigation the run-time behaviour of the project.
At a guess - and it is only a guess - there is a problem somewhere in the IDE which manifests when it has to create an instance of the datamodule so that it's FDConnection can establish the connection needed to open FDQuery1. If that's right, it shouldn't affect the run-time behaviour but like I say, I have not investigated that. If I'm right, I think it's more of an annoyance than a major problem.
Update: This problem seems to be FMX-specific. I repeated steps 1-3 in a new Tokyo VCL project and the exception + error message does not occur, even the first time FDQuery1.Active is set to true.
Update#2: This problem is easily reproducible at run-time. All you need to do is to remove the datamodule from the Project's Forms | AutoCreate list and then, at run-time, attempt to open the FDQuery before the datamodule has been created. Obviously, the work-around is simply to check that the datamodule exists before opening the FDQuery and, if not, to create it in code.
Btw, in a real-world application, personally I wouldn't rely on a TDataSet's Active property setting in the IDE to open the dataset, and always open it in code instead. It's a habit I got into in the early years of Delphi, when there often seemed to be problems with design-time settings of datasets and datasources which referred to a db connection or similar located in another module being "lost" at run-time.

I was getting this same bug using Delphi 10.3 Community Edition in Windows VCL Application. Even though my FDQuery has a valid connection.
I found out in the Embarcadero Delphi CE Bootcamp 2018 course Week 4 of Databases that the problem is because your DataModule was created after your Form.
To fix that you should go to:
Project --> Options --> Application --> Forms
In Autho-create forms you should notice this sequence:
Form1
DataModule1
And you should change the order so DataModule is first:
DataModule1
Form1
In case your connection is placed in the DataModule, you must first connect your connection and then after activated your query. Your query can not be activated before your connection is connected.
Connect in your connection (This can be done in the DataModule);
Activate the query (Let's say it is in your MainForm).
If you create the form before your DataModule, the query will accuse that there is no connection, because the datamodule connection was not created yet.
You also can change the sequence of the created forms programmatically in Project --> View Source.
Check the following code.
program FDSearchInst;
uses
Vcl.Forms,
UnitMain in 'UnitMain.pas' {FormMain},
UnitDataPaths in '..\SharedFiles\UnitDataPaths.pas',
UnitDataModule in '..\SharedFiles\UnitDataModule.pas' {SharedDM: T};
{$R *.res}
begin
ReportMemoryLeaksOnShutdown := True;
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TSharedDM, SharedDM); // Will be created first
Application.CreateForm(TFormMain, FormMain); // Will be created after
Application.Run;
end.
In this code, you may also notice a problem I have been through. There is a comment {SharedDM: T} in front of the DataModule path which is a way to assign a name for your DataModule (when you access the Application --> Forms you will see this name).
This name SharedDM must be different from the file name UnitDataModule, otherwise, it will cause conflict and you'll not be able to run your project.
To explain better, check the following code:
Correct
UnitDataModule in '..\SharedFiles\UnitDataModule.pas' {SharedDM: T};
Incorrect
UnitDataModule in '..\SharedFiles\UnitDataModule.pas' {UnitDataModule: T};

Related

when packing a dbf table, an error is file is in use

When trying to make a request, it displays an error:
File is in use
How can I solve that program?
procedure TForm1.Button4Click(Sender: TObject);
var data,ffg:string;
begin
data:=formatdatetime('ddmm',(DateTimePicker1.Date));
Adoquery2.SQL.Clear;
adoquery2.SQL.text:='Delete from g_rabn where data=data';// deleting data from g_rabn
adoquery2.ExecSQL;
ShowMessage(SysErrorMessage(GetLastError));
end;
procedure TForm1.Button5Click(Sender: TObject);
begin
Adoquery3.close;
Adoquery3.SQL.Clear;
adoquery3.SQl.text:='pack table g_rabn';// packing tablr g_rabn
adoquery3.Open;
ShowMessage(SysErrorMessage(GetLastError));
end;
end.
I can not delete data from the table, they are marked as deleted but require packaging. How to do it programmatically? He writes that file is in use when packing what to do?
You should execute the statement, not open it as a query. One way to achieve that, is to run it using an TADOCommand, not an TADOQuery, or use the ExecSQL method of the TADOQuery.
Also, all other connections to the DBF must be closed, otherwise you can't get the exclusive access that you need for packing the table.
I found this thread from 2005 on another forum, where somebody made this work with two notable parameters:
Using the provider VFPOLEDB.1
Using just the command pack filename.dbf (without the table keyword).
Lastly, I'm not so sure about the line ShowMessage(SysErrorMessage(GetLastError));. This will show you the last API error, but that's on a low level. You are using the ADO components, so if anything is going wrong, you should expect ADO to throw an exception. For all you know ADO already worked around the issue one way or the other, and the error message you're seeing is not even relevant.
I am surprised that I could not get the solution suggested in Golez Troi's answer
to work, especially as it refers to a newsgroup post from someone who had seemingly managed to pack a dBASE table using ADO; as I said in a comment, if I try to call 'Pack xxxx' to pack a dBASE table via ADO, however I do it, I get
Invalid SQL Statement; DELETE, INSERT, PROCEDURE, SELECT or UPDATE expected
.
I was also surprised to notice something in the MS ODBC dBASE docs that I'd not noticed before, namely that the MS ODBC driver for dBASE files requires the BDE
Note
Accessing dBASE ISAM files through the ODBC Desktop Database Drivers requires installation of the Borland database engine
So, seeing as accessing dBASE files via Ado requires the BDE anyway, there seems to me to be
no point avoiding using the BDE to pack the dBASE table using the standard BDE method, namely to call DbiPackTable. I added a TDatabase and TTable
to my ADO test project, after which I was able to execute this code without any problem
procedure TForm1.Button2Click(Sender: TObject);
begin
try
// Insert code here to close any Ado object (TAdoConnection, TAdoCommand, etc) pointing
// at the dBASE table/file
// Also check that not Ado object pointing at it is open in the IDE
//
// Then ...
Database1.DatabaseName := 'MADBF2';
Database1.Connected := True;
Table1.TableName := 'MATest.Dbf';
Table1.Exclusive := True;
Table1.Open;
// Following uses a call to DbiPackTable to pack the target table
Check(DbiPackTable(Table1.DBHandle, Table1.Handle, nil, nil,True));
finally
Table1.Close;
Database1.Connected := False;
end;
end;
FWIW, while I was writing this answer, I noticed that the BDE.Int file (which gives the declarations but not the implementation of the BDE interface) was on the D7 distribution CD but was apparently not installed by default).

Delphi ActiveX MSTSCLib

I'm trying to play around with some Delphi ActiveX library for MS-RDP (mstscax.dll), so I imported the library into my project, and started looking for some codes snippets on the web. On a first look, it's pretty obvious, but the lack of examples makes it a little complex.
First the library gives an error on Delphi Seattle, on this line:
property ConnectWithEndpoint: POleVariant1 write Set_ConnectWithEndpoint;
Ok, I commented this line out (not the best solution, I know), but it compiled. Later I tried to change POleVariant1 to OleVariant only, and still compiling.
Ok, after compiled, I tried this code:
var
Form1: TForm1;
RDP: TMsRdpClient8NotSafeForScripting;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
RDP:= TMsRdpClient8NotSafeForScripting.Create(Nil);
RDP.Server:= 'xxxx';
RDP.AdvancedSettings8.RDPPort:= 3389;
RDP.UserName:= 'terminal';
RDP.AdvancedSettings8.ClearTextPassword:= '123456';
RDP.Connect;
if RDP.Connected.ToBoolean = true then
ShowMessage('connected')
else
ShowMessage('error');
end;
I tried some different types for the var RDP, like TMsRdpClient8 only, but still the same problem:
It don't even try to connect! While looking on the sniffer, no tcp connections are made, just nothing happens and the "error" message appears. Any idea about how to work with this guy?
This question intrigued me so I tried to import that ActiveX control and try it myself. It seems to work for me, so I'll explain what I did.
I imported the mstscax.dll ActiveX control then added it to a new package in order to install components onto the tool palette. I immediately ran into the error you did with the ConnectWithEndpoint property. I changed the type in the declaration to OleVariant because the Set_ConnectWithEnpoint property setter function takes an OleVariant. There is clearly something about the type information that our ActiveX importer code is getting confused by. Either way, that change got the file to compile and install the component package. There are now a bunch of TMsRdpClientXXXX components.
Created a new VCL Forms project, then dropped the TMsRdpClient9 component into the form. Added a TButton and then added this code into the button's OnClick handler:
MsRdpClient91.Server := '<some remote server>';
MsRdpClient91.Domain := 'embarcadero.com';
MsRdpClient91.UserName := 'abauer';
MsRdpClient91.Connect;
Once I ran the app, and pressed the button, it connected and the content of the ActiveX control showed the remote server login screen just fine.
I'm running Windows 10, build 10565.
Here's what I'm seeing on my little app I wrote:

Forget to reset parameter for production version

I use TAdoconnection to access the database.
During development i set the connectionstring to my local database and set connected to true in order to get the information for the other dbcomponents.
When I finalize the program for a customer I forget sometimes to reset the parameter, which leads to an exception on the customer computer because the connectionstring is invalid.
I tried to put in the oncreate event of the datamodule (first line) a connected:=false but it seems to late.
How can I make sure that it the program has the rigth settings for the customer?
I tried to set some parameter in in a conditional compiling phrase like:
{$IFDEF PRODUCT}
param1:=..
....
{$ENDIF}
But I have no clue how to do this for visual components.
You can set true ConnectionString at runtime on event TADOConnection.OnBeforeConnect:
procedure TDM1.ADO1BeforeConnect(Sender: TObject);
begin
ADO1.ConnectionString := 'Provider=SQLOLEDB;......';
end;
Or you can delay connecting
var CanConnect: Boolean;
procedure TDM1.ADO1BeforeConnect(Sender: TObject);
begin
if not CanConnect then Abort;
end;
You cannot use conditionals if dfm files. Which means that if you set properties at design time, then they will be applied at run time, unconditionally.
If you must avoid shipping software with certain properties set, then you must simply stop yourself from doing that. A programmatic solution at run time is not an option.
Personally I would use my version control process to help. Whenever I commit changes I review all the changes. I'd have it is part of my routine to check dfm changes.
On top of that I'd test the software before releasing it. This testing needs to be done in a clean room setting. You'll want a virtual machine snapshot that is representative of the target deployment environment. Testing on your development machine is clearly not a viable option.
As others have said, a source control system could help detect changes to your .DFM files, however, there are also some other tools/ideas at your disposal.
Here are a couple of ideas that will not affect your current deployment procedures too much:
1) Connected property: GExperts GExperts includes an IDE add-in that can detect open connections at compile-time, and automatically set the connected property to false for you. At a minimum, you could use the tool to detect and change manually, or let the tool do it for you. The feature can be found on the GExperts menu: Configuration, Set Component Properties, then add TAdoConnection, Property: Connected, Value: false.
2) ConnectionString: Move your connection string to an .ini file or registry, and have two values, one for development and one for production. Within your code, set each based on a flag, or use:
if debughook <> 0 then
// fetch design-time connection string
else
// fetch production connection string
I would also suggest you look into automating your build process using software such as FinalBuilder.

XE6 ClientDataSet AV attempting to load data from Firebird

I have a minimalist project with FireDac FDConnection & FDSqlQuery,
DataSetProvider and ClientDataSet, trying to access the example
Employee.FDB that came with the Firebird 2.5 package I downloaded
from SourceForge today.
Everything is set to the defaults as theycame off the palette apart from the database name in the FDConnection's pop-up Information tab and the FDQuery's Sql, which is set to select * from employee.
The FDQuery opens fine but as soon as I try
to open the CDS, in the IDE or running my app, I get an access violation.
This is all my code:
FDQuery1.Open;
Caption := IntToStr(FDQuery1.RecordCount); // this shows 42 on the form's caption
CDS1.Open; // AV here
So, the FDQuery opens ok but the CDS doesn't.
At run time, the exception occurs here:
function TCustomClientDataSet.CreateDSBase: IDSBase;
begin
CreateDbClientObject(CLSID_DSBase, IDSBase, Result);
Check(Result.SetProp(dspropANSICODEPAGE, DefaultSystemCodePage)); <-- Exception here
Check(Result.SetProp(dspropUTF8METADATA, NativeUInt(True)));
Check(Result.SetProp(dspropUTF8ERRORMSG, NativeUInt(True)));
end;
The exception msg is
Project FBTest1 raised exception class $C0000005 with
message 'access vioaltion at 0x0075d05b: read of address 0x00000000'.
In the IDE I get a similar exception if I try to set Active = True on the CDS,
which the message said occurred in DSnap200.Bpl.
The first time it happened at run-time I had some kind of "Incident Report" pop-up
offering to report it to Embarcadero. First time I've seen that.
If I substitute a SqlConnection and SqlQuery for the FDac components, I get
the same error.
So, I guess my question is, can a CDS be provoked into this behaviour simply by using default property settings for a project as simple as this one, i.e. did I miss a step, or is it likely an EMBA QC thing?
Solved! Thanks to Graymatter's suggestion to try Using MidasLib, I've got
to the bottom of the problem and fixed it so that the app now works using Midas.Dll. I'm posting this as an answer, rather than a comment, firstly because it's a bit too long for that, but, more importantly, the cause was actually rather strange and the solution may assist anyone else who runs into the problem.
First, I tried Using MidasLib, and the app ran fine, w/o the AV that the q is about.
So, reassured that the problem doesn't arise with the current MidasLib code, I went
back to trying to get the app to work with Midas.Dll. I checked the Midas.Dll
versions in the XE6 bin directory and \Windows\SysWOW64, and they were both as I
was expecting, 20.0.16277.1276 dated 16 June 2014.
Tracing into CheckDBlient in DataSnap.DSIntf and observing carefully, the penny
dropped and I realised what was happening:
procedure CheckDbClient(const CLSID: TGUID);
[...]
begin
[...]
if DbClientHandle = 0 then
begin
Size := 256;
SetLength(FileName, Size);
if RegQueryValue(HKEY_CLASSES_ROOT, PChar(Format('CLSID\%s\InProcServer32',
[GUIDToString(CLSID)])), PChar(FileName), Size) = ERROR_SUCCESS then
SetLength(FileName, Size) else
begin
[...]
end;
DbClientHandle := LoadLibrary(PChar(FileName));
This gets the path to Midas.Dll from the InProcServer key in its registration, and this
wasn't pointing to XE6's bin directory or SysWOW64, but to another location that was not
of my creation and which contained a version of Midas.Dll dating from 2007. Oddly, unlike a copy I have of the Dll dating from the D7 era, this Dll does not have version info on its property page but it has a creation date of 9 August 2002 and a file size of 351Kb.
So, once I'd found that, fixing the problem was as simple as renaming that Dll so that
the OS won't load it, and re-registering the version in SysWOW64.
Where the rogue Midas.Dll came from isn't clear, but it certainly arrived since
last week, because it isn't in the back-up I happen to have from last Thursday evening.
The only things I've installed since then are a handful of 3rd-party GUI utilities for
managing/accessing Access, IB and Firebird databases, so the culprit seems to have been
one of them.

Trapping ADO Provider cannot be found error in Delphi

I have an application written in Delphi that uses an iSeries ODBC connection.
There are some workstations where I do not want to install the iSeries software, and on these workstations, I won't be updating any of these databases anyway.
Is there a way I can trap when this error message is generated? At that point, I can just set a variable like NoUpload to true and not allow the connection on the workstation.
It appears to happen before I ever attempt to even open one of the tables - just by having the ConnectionString set when the application starts fires the message.
Thanks in advance!
You can check the existing ADO providers of the system with ADODB.GetProviderNames
Ideally, you should look for an option to check your condition without an exception being raised. So Sir Rufo's answer is a good place to start.
Another option might be to not include the Provider in the ConnectionString, but set it independently via the Provider property at run-time (most likely only after confirming that it's supported).
However, since you mentioned you're getting an exception before you even attempt to open a table, there are a few things to check (assuming you've been setting up your components at design time):
Have any data sets accidentally been left Active at design time?
Has the Connection been left active at design time?
Are there any options in the ConnectionString that could immediately trigger the error?
Failing the above you could provide a hook for application exceptions. (And really more of a last ditch effort.)
Declare a handler method using with the following signature: TExceptionEvent = procedure (Sender: TObject; E: Exception) of object;. And assign it to Application.OnException. E.g.
procedure Handle(ASender: TObject; E: Exception);
begin
if ISeriesNotInstalledError(E) then
begin
FNoUpload := True;
end
else
begin
Application.ShowException(E);
end;
end;
NOTE: There are some important considerations in following this approach. Since you see this as a standard Use Case, you don't want to be bothering your users with messages. This is also much better than a localised exception handler (a common programming error) because if a caller routine triggers this error you don't want the caller to mistakenly run as if nothing went wrong; when quite clearly something did.

Resources