是否可以在没有 .DFM 的情况下使用数据模块?

Is it possible to using Data Module without .DFM?

我将所有 ADO 引擎卸载到一个单独的数据模块中,因此多个应用程序可以引用一个模块。我所有的应用程序基本上只需要两个 worker 方法来访问数据:

AdoQueryTADODataSet.
的形式传递结果集 AdoExecute 执行简单的 update/delete 查询而不获取任何结果。

这里是 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;

然后我向 class 方法添加了两个公开的包装器。我用它来避免调用中的长 class 引用:

function AdoQuery(const sql: string): TADODataSet;
procedure AdoExecute(const sql: string);

implementation

function AdoQuery(const sql: string): TADODataSet;
begin
  Result := MyDataModule.pvtAdoQuery(sql);
end;

以上是我从所有表单中调用的 worker 函数。

AdoConnect 仅在 DataModuleCreate 事件中运行一次。 TDatModule 派生自 TPersistent,它允许在整个运行时保持单个连接实例。

到目前为止唯一让我烦恼的是一个无用的 .DFM,我根本不需要它。
有没有办法摆脱它?

如果您没有将任何设计时非可视化组件放到您的数据模块上,并且也不打算这样做,那么您根本不需要数据模块。整个目的是用于设计时组件和其他实现,例如 Web 模块甚至 Windows 服务应用程序。但不适用于包装没有设计时组件的纯代码。

另外,如评论中所说,不要混淆TPersistent的意思。 class 的用法完全不同,可以集成到 IDE 对象检查器中(作为组件中的子属性)。

因此,您最理想的做法是将所有内容封装在一个 class 中。为了您的目的,数据库连接...

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;

至于被"persistent"的解释,你可以create/destroy以多种方式加以体现。例如,您可以使用单元的 initializationfinalization 部分(需要 CoInitialize),或者您可以让主窗体在创建时初始化一个全局实例。

这样做的一种常见方法是添加...

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.

第一次从任何地方调用 MyData 时,将实例化一个新的全局实例。然后,每一次它都会重新使用同一个实例。这也解决了 ActiveXCoInitialize 等的需要,因为此时,预计 COM 已经被实例化(这是 ADO 所必需的)。

这个单元的使用非常简单 - 使用将它包含在 uses 任何地方,并通过 MyData 函数访问它的实例。

备注

你应该改掉全局变量的习惯。这是在尝试做以后的工作时自找麻烦。上面的示例显示了如何容纳全局对象实例。所有其他变量应该在该对象中是独立的,或者通常是范围/相关性之一。你的 TADOConnection 的全部控制都应该在这里,包括 connecting/disconnecting、异常处理、分配连接字符串。

我会用两种方式之一处理这种类型的事情,使用接口或继承。在这些情况下,我不想将 classes 暴露给外界。第二个几乎可以称为没有接口的接口:)

接口

此版本returns 包含所需方法的接口。外界只需要使用接口即可。我们将实施细节保密。我们的 TMyDBClass 实现了我们暴露给外界的接口和我们的全局函数 GetDBInterface returns 单个实例。

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.

继承

此版本使用具有所需方法的基础 class。这对人们来说更容易处理,因为它排除了对刚开始的人来说可能很复杂的界面。我们再次向用户隐藏了实现细节,只公开了 shell 或 class,其中包括我们希望人们访问的两个方法。这些方法的实现由继承自暴露的 class 的实现中的 class 执行。我们还有一个全局函数 returns 这个 class 的实例。接口方法相对于此方法的一大优势是该对象的用户不会意外释放该对象。

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.

用法

使用起来非常简单。

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;

如果您可能对完全没有 DataModule 的替代方案感兴趣,请查看以下内容: https://github.com/stijnsanders/xxm/blob/master/Delphi/demo2/03%20Data%20ADO/xxmData.pas

查询存储在单个 .sql 文件中,可以方便地在特定 SQL 编辑器或工作台中对其进行编辑。查询用 --"QueryName" 行分隔,并在启动时加载到查询存储中。假设您大部分时间都查询较小的记录集,最好的锁定和打开方式是只读和静态的,它提供最好的性能和数据库服务器上的最小负载。获取字段值使用 Collect 调用,这也提供了一点性能提升。