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?

+3


source to share


3 answers


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;

      

+4


source


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.

+2


source


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.

0


source







All Articles