Forget to reset parameter for production version - delphi

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.

Related

IBStoredProc does not commit insert if returns data?

I have a stored procedure which insert/update, and then returns result.
create or alter procedure sp_update_system_sticker (
i_sticker_id integer,
i_file_name file_name_type,
i_sticker_name item_name_type,
i_group_id integer)
returns (
o_result integer)
as
begin
update
system_stickers s
set
s.file_name = :i_file_name,
s.name = :i_sticker_name,
s.fk_stickers_groups = :i_group_id
where
s.id = :i_sticker_id;
o_result = -1;
suspend;
end
I am setting it in Delphi in a IBStoredProc, and execute it as follow:
procedure TDataModule_.updateSystemSticker(stickerId, groupId: integer;
stickerName, fileName: String);
var
r : Integer;
begin
with IBStoredProc_UpdateSystemSticker do
begin
Transaction.Active := true;
ParamByName( 'I_STICKER_ID' ).AsInteger := stickerId;
ParamByName( 'I_GROUP_ID' ).AsInteger := groupID;
ParamByName( 'I_STICKER_NAME' ).AsString := stickerName;
ParamByName( 'I_FILE_NAME' ).AsString := fileName;
ExecProc;
Transaction.Commit;
end;
end;
Anyway it does not commit the result into the database.
If I remove the returns - it start to commit.
How to execute and commit properly stored procedure with IBStoreProc which returns results ?
The problem is the presence of SUSPEND. This makes your stored procedure a selectable procedure, and not an executable procedure. When you use a selectable procedure, then all work done since the previous fetched row will be undone when the cursor is closed (which happens on commit). If you fetched nothing, this means that it is as if no work was performed by the stored procedure*.
In other words, you need to remove the SUSPEND (an executable stored procedure outputs a single row immediately on execute without having to wait for a fetch).
I don't program Delphi, so I can't comment on the specifics of getting results in Delphi.
*: Recent versions of Firebird can prefetch rows, so this might not be entirely accurate
Since you made it a "selectable stored procedure" by adding SUSPEND PSQL statement - just do a select from it.
Use regular TIBQuery instead of TIBStoredProc with a command like
select * from StoredProcedureName( :input_param1, :input_param2, :input_param3 )
I would not recommended to use IBExpress to directly call stored procedures in Firebird. Interbase turned up to have a bug in stored procedures execution, AFAIR something wrong with errors handling. To counter it IBX team added an intentional bug of executing SPs twice (under some conditions), which usually (not always) neutralized the Interbase bug. When Firebird team fixed the server bug - this IBX counter-bug started breaking data. IBX team refused to revert to normal behavior for Firebird databases as Firebird was considered competitor to Interbase.
Equally, IBX would not support Firebird-specific changes made after Interbase 6.x/Firebird 0.9 split. For example:
client DLL name change to avoid collision: it became fbclient.dll or fbembed.dll from gds32.dll, however IBX only supports the legacy name. It is hardcoded and can not be changed. If you have IBX sources you may patch it and recompile the library - but why bother?
Firebird's new datatypes like 64-bit integer and boolean. Again, if you have IBX sources...
Firebird's new APIs are not supported.
That said, there is an IBX add-on library by Dmitry Loginov, IBX FB Utils, and it has a number of rather comfy wrappers, on top of IBX. It alone might be a good pro-IBX argument.
FPC (FreePascal) folks started IBX fork they named IBX2, which would hopefully have first-class support for Firebird. I do not know about quality and development speed of it, but i suspect it might appear an easiest migration route out of IBX, or not.
Personally for Firebird-centric Delphi projects i prefer opensource UIB (Unified Interbase) library. However
Being "lean thin API wrapper" it is not TDataSet derived, albeit having a read-only TDataSet wrapper and trying to keep API closely resembling one of TDataSet.
being Henri's Delphi project it has little documentation (tests and examples mostly) and is abandoned by the author (albeit some other guy was adding patches later)
it has neat features like SQL scripter component (but you might need to extend it to support all Firebird new SQL commands, at least i did it to support FB2's MERGE) and for RECORD in SQLQuery do... loop (albeit you can extract it and make it into a separate add-on over any your DB library of choice)

Issue with TFDQuery on a form connecting to a datamodule

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};

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.

Method in Delphi to set database connections to disconnected upon compile

Is there a method or compiler directive or some way of assuring certain components, such as queries or database connections get set to active=false or disconnected when you run a build/compile? Seems so often these are turned on by something else and you don't notice it until its too late.
My particular install is Delphi 7
The Set Component Properties feature of GExperts is able to do that.
i think the best option would be to subclass stock connection component and in your own one override .Loaded method like that
if not csDesigning in Self.ComponentState then
if not Self.ActiveInDFM {new boolean published property} then
if Self.Active then Self.Active := false;
inherited;
http://docwiki.embarcadero.com/Libraries/XE3/en/System.Classes.TComponentState
http://docwiki.embarcadero.com/Libraries/XE3/en/System.Classes.TComponent.Loaded
By (ab)using Delphi Form Designer stupidness you can use it even without actually installing your new component into IDE Palette - just give it the same name as to the stock component class, then put your own method as last in the form's interface-uses list: thus in design-time you would have stock component and when compiling it would be transparently substituted with your own one.
Or you can sub-class it right above the very form declaration like (for another component):
type
TPanel = class(ExtCtrls.TPanel)
private
...
TForm1 = class(TForm) ....
I guess this approach might be seen as analogue to aspect-oriented programming, using limitations of IDE in developer-benefitting way.
Another approach might be some script, that cleans .Active properties in DFM on save or before build, but this way is complex for
i may be harder to integrate with stand-alone build severs (new script for each different CI framework tried)
it would reset Active property for design-time as well. This is a proper thing to do, from rigorous point of view. Yet this might be not very convenient.
You may just use similar code in your Form's and DataModule's .Loaded method (you would have to override it instead connection's method then).
You can copy-paste the same code into every Form's Loaded method.
procedure TMyForm.Loaded; // override
var c: TComponent; i: integer;
begin
try
for i := 0 to Self.ComponentsCount - 1 do begin
c := Self.Components[i];
if c is TCustomConnection then
with TCustomConnection(c) do // Hate those redundant typecasts!
if Connected then Connected := false;
if c is TDataSet then
with TDataSet(c) do // Delphi could took a lesson from Component Pascal
if Active then Active := false;
if c is ... // transactions, stored procedures, custom libriaries...
end;
finally
inherited;
end;
end;
This seems to be less sly way - thus most reliable. Yet that is a lot if copy-paste, and if you would later add some new component or library, that may ask for modifying copy-pasted code in all the forms.
You may centralize this code in some MyDBUtils unit into global procedure like Disconnect(const owner: TComponent); and then
procedure TMyForm.Loaded; // override
var c: TComponent; i: integer;
begin
try
MyDBUtils.Disconnect(Self);
finally
inherited;
end;
end;
This approach also has drawbacks though:
This would make MyDBUtils unit tightly coupled with all and every the database-related libs and components you might use. For large inherited projects, consisting of different binary modules, historically based on different db-access libraries and in process of migration, thus pulling all the access libraries into every binary module.
It can be overcome by ad hoc DI framework, but then the opposite can happen: you risk under-delivering, you may just forget to inject some library or component handler into the project that actually use it or got modified to use it.
If your form would have some components, whose connectivity should NOT be reset (object arrays as datasets, in-memory tables, in-memory NexusDB or SQLite databases, etc), you'd have to come up with ad hoc non-obvious convention to opt them out.
In my applications, I set my connection's Tag property to 1 at design time. In the OnBeforeConnect event, I check Tag, and if it is equal to 1, I abort the connection and set it to 0.

Does TQuery.Unprepare close the query result in Delphi?

I wonder whether in Delphi calling
Query1.Unprepare;
implicitly closes Query1, if it was previously active. Such that e.g. calling Next on it will fail.
You might say, just go ahead and try but I did on a 64-bit Windows 7 system and had all sort of problems with it until finally my BDE Administrator seems to be completely broken. So I decided to just ask this questions before I start to find out, how I can get BDE running on my system ;-)
You can not use Prepare/Unprepare on an open dataset. you need to close it first.
unit DBTables;
...
procedure TQuery.SetPrepared(Value: Boolean);
begin
if Handle <> nil then DatabaseError(SDataSetOpen, Self);
...
// SDataSetOpen = 'Cannot perform this operation on an open dataset';

Resources