Номер статьи: KB000016
Связанные блоки:
Есть несколько вариантов выполнения запросов к БД в пользовательском приложении: через стандартные программные интерфейсы или через объекты репозитория «Запрос» или «Процедура».
Первый вариант предполагает выполнение запроса через интерфейсы сборки Dal. Если используется сложный по структуре запрос, то данный метод имеет ряд недостатков:
увеличивается трудоемкость редактирования запроса;
ухудшается читаемость кода пользовательского приложения.
Рассмотрим пример выполнения параметрического запроса, которые добавляет запись в какую-либо таблицу базы данных. Значения полей передаются посредством входных параметров функции. Для выполнения примера предполагается наличие в репозитории объекта «База данных» с идентификатором «OBJ_DB». Результатом работы функции будет количество добавленных записей:
Function ChangeData(Value1: Variant; Value2: Variant): Integer;
Var
Mb: IMetabase;
DbIns: IDatabaseInstance;
Command: IDalCommand;
CommandParams: IDalCommandParams;
SQL: String = "";
Result: Integer;
Begin
Mb := MetabaseClass.Active;
DbIns := Mb.ItemById("OBJ_DB").Open(Null) As IDatabaseInstance;
SQL := "Insert into ... values (:Val1, :Val2)";
Command := DbIns.Connection.CreateCommand(SQL);
Command.Parse;
CommandParams := Command.Params;
CommandParams.Item(0).Value := Value1;
CommandParams.Item(1).Value := Value2;
Result := Command.Execute;
Return Result;
End Function ChangeData;
Пример извлечения значений. Результатом работы функции будет являться курсор с результирующим набором данных:
Function ReadData: IDalCursor;
Var
Mb: IMetabase;
DbIns: IDatabaseInstance;
Com: IDalCommand;
SQL: String;
Result: IDalCursor;
Begin
Mb := MetabaseClass.Active;
DbIns := Mb.ItemById("OBJ_DB").Open(Null) As IDatabaseInstance;
SQL := "Select ...";
Com := DbIns.Connection.CreateCommand(SQL);
Result := Com.CreateCursor;
Return Result;
End Function ReadData;
Второй вариант предполагает использование объектов репозитория «Запрос» или «Процедура» с необходимыми наборами параметров. В приложении должна быть функция, позволяющая получать результат выполнения этого запроса. Преимущества данного способа:
объект репозитория хранится в определенном месте и его можно легко его редактировать;
уменьшается количество кода прикладного приложения, увеличивается эффективность его отладки.
Пример получения данных путем выполнения объекта «Запрос». Идентификатор запроса в репозитории передается в параметре функции QueryId, идентификатор параметра запроса - ParamId, значение параметра - ParamValue. Результатом работы функции будет открытый экземпляр объекта, предоставляющий доступ к кэшированным данными:
Public Function RunQueryWithParam(QueryId: String; ParamId: String; ParamValue: Variant): IDatasetInstance;
Var
Mb: IMetabase;
QueryKey: Integer;
Desc: IMetabaseObjectDescriptor;
Pars: IMetabaseObjectParamValues;
Begin
Mb := MetabaseClass.Active;
QueryKey := Mb.GetObjectKeyById(QueryId);
If QueryKey = -1 Then
Return Null;
End If;
Desc := Mb.Item(QueryKey);
Pars := Desc.Params.CreateEmptyValues;
Pars.FindById(ParamId).Value := ParamValue;
Return Desc.Open(Pars) As IDatasetInstance;
End Function RunQueryWithParam;
В приложении код будет выглядеть следующим образом:
Var
Tasks: IDatasetInstance;
Begin
Tasks := RunQueryWithParam(< Идентификатор запроса >, "ID", < Значение параметра >);
Для получения доступа к данным можно использовать свойство IDatasetInstance.Fields, либо обратиться к кэшу источника посредством метода IDatasetInstance.OpenCached:
Var
Tasks: IDatasetInstance;
TasksData: ICachedDataset;
Begin
Tasks := RunQueryWithParam(< Идентификатор запроса >, "ID", < Значение параметра >);
TasksData := Tasks.OpenCached;
Для передачи параметру множественного значения можно использовать следующую конструкцию:
Var
Tasks: IDatasetInstance;
Ls: ArrayList;
Begin
Ls := New ArrayList.Create;
For i := 1 To 2 Do
Ls.Add(i);
End For;
Tasks := RunQueryWithParam(< Идентификатор запроса >, "ID", Ls.ToArray);
Настройки серверов СУБД накладывают ограничение на количество одновременно открытых курсоров, поэтому необходимо своевременно закрывать неиспользуемые курсоры с помощью метода Close.
При реализации поддержки драйверов Microsoft SQL Server 2008 был задействован режим работы MARS (Multiple Active Result Set). Использование данного режима позволяет в рамках одного соединения иметь более одного открытого результирующего набора данных. Однако, пока данные одного результирующего набора не дочитаны до конца, то невозможно начать новую транзакцию в рамках этого соединения. Для решение данной проблемы существует два варианта решения:
Использовать разные соединения. Для этого можно создать разные объекты «База данных» в репозитории, либо создавать подключение непосредственно в прикладном коде.
Считывать до конца результирующий набор данных. При работе с набором данных, который описывает интерфейс IDatasetInstance, для считывания всех записей необходимо открыть кэш и получить значение свойства ICachedDataset.RecordCountAll. Обращение к данному свойству приведет к считыванию всех записей.
Также стоит учитывать рекомендации по разработке приложений, функционирующих в режиме MARS. Данные рекомендации представлены в MSDN.
При формировании запросов необходимо учитывать синтаксис, который используется при работе с конкретной СУБД. Объекты репозитория «Запрос» и «Процедура» позволяют указывать синтаксис сразу для нескольких СУБД, это может использоваться при переносе объектов между репозиториями, развернутыми на различных СУБД.
Важным аспектом являются параметры объектов «Запрос» и «Процедура», которые в SQL-запросах используются для возврата значений. Такие параметры необходимо явно приводить к типу возвращаемого значения. Это связано с возможностями преобразования данных различных СУБД, а также с особенностями работы драйверов платформы. Пример запроса с преобразованием типа параметра:
select cast(:param as <type>) as param
См. также: