SQLite во многих случаях является удобным, незаменимым инструментом. Я уже не могу себе представить - как мы все жили без него. Тем не менее, есть некоторые неудобства при его использовании, связанные с тем, что это легкая встраиваемая СУБД.
Самое большое неудобство для меня, как Delphi-разработчика - отсутствие хранимых процедур. Я очень не люблю смешивать Delphi-код и SQL-скрипты. Это делает код намного менее читабильным, и затрудняет его поддержку. Следовательно, нужно как-то разнести код Delphi и тексты SQL-скриптов.
Предлагаю свой вариант решения проблемы
-
Выносим весь SQL-код в отдельный тестовый файл ресурсов, подключенный к проекту.
-
Запросы в SQL-файле разделяем маркерами начала с идентификаторами и маркерами конца. В моём случае синтаксис маркера начала - //SQL ИмяПроцедуры. Маркер конца - GO.
-
Создаем класс - менеджер SQL-запросов. При загрузке приложения он читает SQL-файл из ресурсов и составляет из него список хранимых процедур с уникальными именами-идентификаторами.
-
В процессе работы приложения мендежер извлекает текст SQL-запроса по его идентификатору для последующей его передачи на выполнение.
Главная идея - простота и легкость использования, подобная вызову хранимых процедур и удобство при создании и модификации SQL-запросов
Код юнита менеджера запросов:
unit uSqlList;interfaceuses System.Classes, Winapi.Windows, System.SysUtils, System.Generics.Collections;type TSqlList = class(TObjectDictionary<string, TStrings>) 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;implementationfunction 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;initializationSqlList := TSqlList.Create;finalizationFreeAndNil(SqlList);end.
Пример содержимого файла SQL-скриптов:
//SQL GetOrderSELECT * FROM Orders WHERE ID = :IDGO//SQL DeleteOpenedOrdersDELETE FROM Orders WHERE Closed = 0GO
Подключение файла скриптов к проекту:
{$R 'SqlList.res' '..\Common\DataBase\SqlList.rc'}
Использование с компонентом TFDConnection:
Connection.ExecSQL(SqlList['GetOrder'], ['123']);
Собственно, это всё. Использую данное решение уже в нескольких проектах и мне оно кажется очень удобным. Буду благодарен за советы и замечания. Рад, если мой посто кому-то будет полезен!