Можно ли использовать модуль данных без.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
вызов, который также предлагает небольшой прирост производительности.