Delphi и SQLite. Альтернатива хранимым процедурам
SQLite во многих случаях является удобным, незаменимым инструментом. Я уже не могу себе представить — как мы все жили без него. Тем не менее, есть некоторые неудобства при его использовании, связанные с тем, что это легкая встраиваемая СУБД.
Самое большое неудобство для меня, как Delphi-разработчика — отсутствие хранимых процедур. Я очень не люблю смешивать Delphi-код и SQL-скрипты. Это делает код намного менее читабильным, и затрудняет его поддержку. Следовательно, нужно как-то разнести код Delphi и тексты SQL-скриптов.
Предлагаю свой вариант решения проблемы
Выносим весь SQL-код в отдельный тестовый файл ресурсов, подключенный к проекту.
Запросы в SQL-файле разделяем маркерами начала с идентификаторами и маркерами конца. В моём случае синтаксис маркера начала — //SQL ИмяПроцедуры. Маркер конца — GO.
Создаем класс — менеджер SQL-запросов. При загрузке приложения он читает SQL-файл из ресурсов и составляет из него список хранимых процедур с уникальными именами-идентификаторами.
В процессе работы приложения мендежер извлекает текст SQL-запроса по его идентификатору для последующей его передачи на выполнение.
Главная идея — простота и легкость использования, подобная вызову хранимых процедур и удобство при создании и модификации SQL-запросов
Код юнита менеджера запросов:
unit uSqlList;
interface
uses System.Classes, Winapi.Windows, System.SysUtils,
System.Generics.Collections;
type
TSqlList = class(TObjectDictionary)
const
SCRIPTS_RCNAME = 'SqlList';
private
function GetScripts(const AName: string): TStrings;
procedure FillList;
function GetItem(const AKey: string): string;
public
constructor Create;
public
property Sql[const Key: string]: string read GetItem; default;
end;
var
SqlList: TSqlList;
implementation
function GetStringResource(const AName: string): string;
var
LResource: TResourceStream;
begin
LResource := TResourceStream.Create(hInstance, AName, RT_RCDATA);
with TStringList.Create do
try
LoadFromStream(LResource);
Result := Text;
finally
Free;
LResource.Free;
end;
end;
{ TScriptList }
constructor TSqlList.Create;
begin
inherited Create([doOwnsValues]);
FillList;
end;
procedure TSqlList.FillList;
var
LScripts: TStrings;
I: Integer;
S, LKey: string;
LStarted: Boolean;
LSql: TStrings;
begin
LScripts := GetScripts(SCRIPTS_RCNAME);
try
LStarted := False;
LSql := nil;
for I := 0 to LScripts.Count - 1 do
begin
S := LScripts[I];
if LStarted then
begin
if S = 'GO' then
begin
LStarted := False;
Continue;
end
else if not S.StartsWith('//') then
LSql.Add(S);
end
else
begin
LStarted := S.StartsWith('//SQL ');
if LStarted then
begin
LKey := S.Substring(6);
LSql := TStringList.Create;
Add(LKey, LSql);
end;
Continue;
end;
end;
finally
LScripts.Free;
end;
end;
function TSqlList.GetItem(const AKey: string): string;
begin
Result := Items[AKey].Text;
end;
function TSqlList.GetScripts(const AName: string): TStrings;
begin
Result := TStringList.Create;
try
Result.Text := GetStringResource(AName);
except
FreeAndNil(Result);
raise;
end;
end;
initialization
SqlList := TSqlList.Create;
finalization
FreeAndNil(SqlList);
end.
Пример содержимого файла SQL-скриптов:
//SQL GetOrder
SELECT * FROM Orders WHERE ID = :ID
GO
//SQL DeleteOpenedOrders
DELETE FROM Orders WHERE Closed = 0
GO
Подключение файла скриптов к проекту:
{$R 'SqlList.res' '..\Common\DataBase\SqlList.rc'}
Использование с компонентом TFDConnection:
Connection.ExecSQL(SqlList['GetOrder'], ['123']);
Собственно, это всё. Использую данное решение уже в нескольких проектах и мне оно кажется очень удобным. Буду благодарен за советы и замечания. Рад, если мой посто кому-то будет полезен!