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<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; 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']);
Собственно, это всё. Использую данное решение уже в нескольких проектах и мне оно кажется очень удобным. Буду благодарен за советы и замечания. Рад, если мой посто кому-то будет полезен!
ссылка на оригинал статьи https://habr.com/ru/post/563690/
Добавить комментарий