Is it possible to use a data module without .DFM?
I dumped the entire ADO hood into a separate data module, so one module can be passed by multiple applications. All my applications basically only need two working methods to access data:
AdoQuery
provides a set of results in a form TADODataSet
. AdoExecute
performs simple update / delete queries without getting any results.
Here is the structure of the class:
type
TMyDataModule = class(TDataModule)
procedure DataModuleCreate(Sender: TObject);
procedure DataModuleDestroy(Sender: TObject);
private
procedure pvtAdoConnect;
procedure pvtAdoExecute(const sql: string);
function pvtAdoQuery(const sql: string): TADODataSet;
public
AdoConnection: TADOConnection;
end;
Then I added two public wrappers to the class methods. I used this to avoid long class calls in calls:
function AdoQuery(const sql: string): TADODataSet;
procedure AdoExecute(const sql: string);
implementation
function AdoQuery(const sql: string): TADODataSet;
begin
Result := MyDataModule.pvtAdoQuery(sql);
end;
Above is a working function that I call from all of my forms.
AdoConnect
is executed only once per event DataModuleCreate
. TDatModule derived from TPersistent
, which keeps a single instance of the connection throughout the runtime.
The only thing that annoys me is the useless .DFM which I don't need at all.
Is there any option to get rid of it?
source to share
I would handle this type of thing in one of two ways: with interfaces or with inheritance. In these cases, I prefer not to expose the classes to the outside world. The second could be called an interface without interfaces :)
Interfaces
This version returns an interface that includes the required methods. The outside world only needs to use the interface. We keep the implementation details private. Our TMyDBClass implements the interface that we exposed to the outside world, and our global function GetDBInterface
returns one instance.
interface
uses
ADODB;
type
IMyDBInterface = interface
['{2D61FC80-B89E-4265-BB3D-93356BD613FA}']
function AdoQuery(const sql: string): TADODataSet;
procedure AdoExecute(const sql: string);
end;
function GetDBInterface: IMyDBInterface;
implementation
type
TMyDBClass = class(TInterfacedObject, IMyDBInterface)
strict private
FConnection: TADOConnection;
protected
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
public
function AdoQuery(const sql: string): TADODataSet;
procedure AdoExecute(const sql: string);
end;
var
FMyDBInterface: IMyDBInterface;
procedure TMyDBClass.AdoExecute(const sql: string);
begin
// ...
end;
function TMyDBClass.AdoQuery(const sql: string): TADODataSet;
begin
// ...
end;
procedure TMyDBClass.AfterConstruction;
begin
inherited;
FConnection := TADOConnection.Create(nil);
end;
procedure TMyDBClass.BeforeDestruction;
begin
FConnection.Free;
inherited;
end;
// Our global function
function GetDBInterface: IMyDBInterface;
begin
if not Assigned(FMyDBInterface) then
FMyDBInterface := TMyDBClass.Create;
Result := FMyDBInterface;
end;
initialization
finalization
FMyDBInterface := nil;
end.
Inheritance
This version uses a base class that has the required methods. It's a little easier for people because it eliminates the interface, which can be tricky for beginners. Again, we hide the implementation details from the user and only expose the wrapper for the class, which includes the two methods we want to access. The implementation of these methods is done by the class in the implementation, which inherits from the public class. We also have a global function that returns an instance of this class. The big advantage that the front-end approach has in this regard is that the user of that object cannot accidentally release the object.
interface
uses
ADODB;
type
TMyDBClass = class(TObject)
public
function AdoQuery(const sql: string): TADODataSet; virtual; abstract;
procedure AdoExecute(const sql: string); virtual; abstract;
end;
function GetDBClass: TMyDBClass;
implementation
type
TMyDBClassImplementation = class(TMyDBClass)
strict private
FConnection: TADOConnection;
protected
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
public
function AdoQuery(const sql: string): TADODataSet; override;
procedure AdoExecute(const sql: string); override;
end;
var
FMyDBClass: TMyDBClassImplementation;
procedure TMyDBClassImplementation.AdoExecute(const sql: string);
begin
inherited;
// ...
end;
function TMyDBClassImplementation.AdoQuery(const sql: string): TADODataSet;
begin
inherited;
// ...
end;
procedure TMyDBClassImplementation.AfterConstruction;
begin
inherited;
FConnection := TADOConnection.Create(nil);
end;
procedure TMyDBClassImplementation.BeforeDestruction;
begin
FConnection.Free;
inherited;
end;
// Our global function
function GetDBClass: TMyDBClass;
begin
if not Assigned(FMyDBClass) then
FMyDBClass := TMyDBClassImplementation.Create;
Result := FMyDBClass;
end;
initialization
FMyDBClass := nil;
finalization
FMyDBClass.Free;
end.
Using
Using these programs is very simple.
implementation
uses
MyDBAccess; // The name of the unit including the code
procedure TMyMainForm.DoSomething;
var
myDataSet: TADODataSet;
begin
myDataSet := GetDBInterface.AdoQuery('SELECT * FROM MyTable');
...
// Or, for the class version
myDataSet := GetDBClass.AdoQuery('SELECT * FROM MyTable');
...
end;
source to share
Unless you have any non-visual design-time components dumped onto your data module, and you don't plan to do so, you don't need a data module at all. The whole target is in design-time components and other implementations like a web module or even a Windows service application. But not for packaging clean code without design-time components.
Also, as mentioned in the comments, do not confuse the meanings TPersistent
. This class is used in a completely different way and can be integrated into the IDE Object Inspector (as sub-properties within a component).
So the ideal thing for you is to encapsulate everything in one class. For your purpose, a database connection ...
type
TMyData = class(TObject)
private
FConnection: TADOConnection;
public
constructor Create;
destructor Destroy; override;
procedure pvtAdoConnect;
procedure pvtAdoExecute(const sql: string);
function pvtAdoQuery(const sql: string): TADODataSet;
...
end;
implementation
{ TMyData }
constructor TMyData.Create;
begin
FConnection:= TADOConnection.Create(nil);
end;
destructor TMyData.Destroy;
begin
FConnection.Connected:= False;
FConnection.Free;
inherited;
end;
As far as interpreting "persistence" is concerned, you can instantiate / destroy it in different ways. For example, you can use sections initialization
and finalization
(requiring CoInitialize
), or you can create a master form to create a global instance on creation.
One common way to do this is to add ...
interface
function MyData: TMyData;
implementation
var
_MyData: TMyData;
function MyData: TMyData;
begin
if not Assigned(_MyData) then
_MyData:= TMyData.Create;
Result:= _MyData;
end;
initialization
_MyData:= nil;
finalization
_MyData.Free;
end.
The first time it is called MyData
from anywhere, a new global instance will be instantiated. Then, every time it reuses the same instance. This also resolves the need ActiveX
, CoInitialize
and so on, since at this point the COM is expected to be created (which is required for ADO).
Using this device would be extremely simple - use it uses
anywhere and access an instance of it using a function MyData
.
Notes
You should get out of the habit of global variables. It gets in trouble on the road when you try to do later work. The above example shows how to place an instance of a global object. All other variables should be self-contained within this object, or in general scope / relevance. Full control over yours TADOConnection
should be here, including connection / disconnection, exception handling, connection string assignment.
source to share
If you might be interested in an alternative without DataModules alltogether, have a look at this: https://github.com/stijnsanders/xxm/blob/master/Delphi/demo2/03%20Data%20ADO/xxmData.pas
Queries are stored in a single file .sql
, which is convenient for editing in special SQL editors or versts. Queries are separated by a line with --"QueryName"
and loaded into the Query Store at startup. Assuming you are querying smaller records most often, the best locking and open style is read-only and static, which provides the best performance and the least load on the database server. Retrieving field values ββuses a call Collect
, which also provides a slight performance boost.
source to share