Можно ли использовать модуль данных без.DFM?

Я разгрузил все ADO в отдельном модуле данных, поэтому один и тот же модуль может использоваться несколькими приложениями. Все мои приложения в основном нуждаются в двух рабочих методах для доступа к данным:

AdoQuery предоставляет набор результатов в виде TADODataSet,
AdoExecute выполняет простые запросы обновления / удаления без получения каких-либо результатов.

Вот структура класса:

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;

Затем я добавил две открытые обертки для методов класса. Я использовал это, чтобы избежать длинных ссылок на классы в вызовах:

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

implementation

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

Выше - рабочая функция, которую я вызываю из всех своих форм.

AdoConnect работает только один раз на DataModuleCreate событие. TDatModule получен из TPersistent, что позволяет сохранить один экземпляр соединения в течение всего времени выполнения.

Пока единственное, что меня раздражает - это бесполезный.DFM, который мне совсем не нужен.
Есть ли возможность избавиться от этого?

3 ответа

Решение

Я бы справился с этим типом вещи одним из двух способов, с интерфейсами или с наследованием. Я предпочитаю не выставлять классы внешнему миру в этих случаях. Второй почти можно назвать интерфейсом без интерфейсов:)

Интерфейсы

Эта версия возвращает интерфейс, который включает в себя необходимые методы. Внешний мир должен использовать только интерфейс. Мы сохраняем детали реализации в тайне. Наш TMyDBClass реализует интерфейс, который мы открыли для внешнего мира, и нашу глобальную функцию GetDBInterface возвращает единственный экземпляр.

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.

наследование

Эта версия использует базовый класс, который имеет необходимые методы. Людям немного легче иметь дело с этим, потому что он исключает интерфейс, который может быть сложным для начинающих. Опять же, мы скрываем детали реализации от пользователя и открываем только оболочку класса, включающую два метода, к которым мы хотим, чтобы люди обращались. Реализация этих методов выполняется классом в реализации, которая наследуется от открытого класса. У нас также есть глобальная функция, которая возвращает экземпляр этого класса. Большое преимущество интерфейсного подхода перед этим состоит в том, что пользователь этого объекта не может случайно освободить объект.

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;

Если у вас нет каких-либо невизуальных компонентов времени разработки, добавленных в ваш модуль данных, и вы не планируете этого делать, тогда вам вообще не нужен модуль данных. Все это предназначено для компонентов времени разработки и других реализаций, таких как веб-модуль или даже приложение-служба Windows. Но не для упаковки чистого кода без компонентов времени разработки.

Кроме того, как уже упоминалось в комментариях, не следует путать значение TPersistent, Этот класс используется совершенно по-разному и может быть интегрирован в Инспектор объектов IDE (как подчиненные свойства внутри компонента).

Таким образом, идеальная вещь для вас - это заключить все в один класс. Для вашей цели соединение с базой данных...

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;

Что касается интерпретации "постоянства", вы можете создать / уничтожить его экземпляр разными способами. Например, вы можете использовать initialization а также finalization разделы (требующие 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 откуда угодно, будет создан новый глобальный экземпляр. Затем, каждый раз, когда он снова использует один и тот же экземпляр. Это также решает необходимость ActiveX а также CoInitialize и т.д., потому что на этом этапе ожидается, что COM уже будет создан (что требуется для ADO).

Использование этого устройства было бы чрезвычайно просто - используйте его в uses и получить доступ к его экземпляру через MyData функция.

Заметки

Вы должны выйти из привычки глобальных переменных. Это напрашивается на неприятности в будущем при попытке сделать более позднюю работу. В приведенном выше примере показано, как разместить экземпляр глобального объекта. Все остальные переменные должны быть самодостаточными внутри этого объекта или, как правило, в области видимости / релевантности. Весь контроль над вашим TADOConnection должно быть внутри, в том числе подключение / отключение, обработка исключений, назначение строки подключения.

Если вас может заинтересовать альтернатива без DataModules, взгляните на это: https://github.com/stijnsanders/xxm/blob/master/Delphi/demo2/03%20Data%20ADO/xxmData.pas

Запросы хранятся в одном .sql файл, который удобно редактировать в определенных редакторах SQL или на рабочих местах. Запросы разделяются строкой с --"QueryName"и загружается в хранилище запросов при запуске. Предполагая, что вы делаете запросы для небольших наборов записей большую часть времени, лучший стиль блокировки и открытия - это только чтение и статический, что обеспечивает наилучшую возможную производительность и минимальную нагрузку на сервер базы данных. Получение значений поля использует Collect вызов, который также предлагает небольшой прирост производительности.

Другие вопросы по тегам