Работа с объектами СУБД Caché на примере Delphi


    Несмотря на перманентные похороны Delphi, эта платформа построения Desktop приложений живёт и здравствует, а со сменой владельца даже обретает второе дыхание и продолжает оставаться основным инструментом для тысяч разработчиков во всём мире.
    Как и с любыми другими СУБД, Delphi прекрасно взаимодействует с СУБД Caché.

    Из Delphi можно подключиться к Caché, используя следующие интерфейсы:


    В данной статье будут рассмотрены примеры использования объектного интерфейса при работе с СУБД Caché.


    Но для начала приведу несколько коротких примеров на VBScript, которые вы можете запустить непосредственно из Windows Проводника.

    Пример прямого доступа:
    Set f = CreateObject("VISM.VisMCtrl.1")
    f.Server="CN_IPTCP:localhost[1972]:_system:@ SYS"
    f.NameSpace="SAMPLES"
    f.Execute("=$zv") 'получение версии СУБД
    WScript.Echo f.VALUE

    Пример реляционного доступа:
    Set cn=Createobject("ADODB.Connection") 
    cn.ConnectionString="DRIVER={InterSystems ODBC35}; SERVER=127.0.0.1; PORT=1972; DATABASE=SAMPLES; UID=_system; PWD=SYS"
    cn.open
    WScript.Echo "Succesfully!"
    

    Пример объектного доступа:
    Set f = CreateObject("CacheActiveX.Factory")
    Set rs = CreateObject("CacheActiveX.ResultSet")
    If Not f.IsConnected() Then
    
      f.Connect("cn_iptcp:127.0.0.1[1972]:SAMPLES:_SYSTEM:SYS")
    
      Set rs=f.DynamicSQL("select TOP 3 * from Sample.Person")
      rs.Execute()
      while rs.Next
        WScript.Echo rs.Get("SSN") 'выводим поле SSN первых трёх записей из таблицы Sample.Person
      wend
    
      rs.Close()
      Set person = f.Static("Sample.Person")
      age=person.CurrentAge(45678) 'вызываем метод класса Sample.Person
    
      WScript.Echo age
    End If
    

    Похожим образом вы можете работать с СУБД Caché, использя JScript, Visual Basic, C++ Builder, и т.д.
    Примечание: Для Java и .NET предусмотрено несколько нативных интерфейсов, которые обеспечивают гораздо больше возможностей.

    Позднее и раннее связывание


    Существует два подхода при работе с объектами Caché из Delphi:

    У каждого из этих подходов есть свои преимущества и недостатки, которые впрочем компенсируют друг друга.

    При позднем связывании разработчику недоступен подсказчик кода, соответственно высока вероятность допустить ошибку в коде, которая будет обнаружена лишь во время выполнения программы. Скорость работы несколько ниже, чем при раннем связывании, так как код выполняется динамически.

    Раннее связывание лишено этих недостатков, но за это приходится платить тем, что при изменении интерфейса пользовательских классов в БД или при переходе на новую версию СУБД Caché необходимо перегенерировать прокси-классы для клиентского приложения.

    Комбинируя оба этих подхода можно добиться оптимальной производительности и удобства в работе.

    В данной статье будет в основном использоваться раннее связывание, так как данный подход меньше описан по сравнению с поздним связыванием.

    Для работы Caché Objects используются две библиотеки, которые по умолчанию устанавливаются в C:\Program Files\Common Files\InterSystems\Cache\:
    • CacheObject.dll (устаревшая);
    • CacheActiveX.dll (рекомендуемая).

    Начиная с версии Caché 5.1, разработчики InterSystems настоятельно рекомендуют использовать новую библиотеку CacheActiveX.dll. Библиотека CacheObject.dll оставлена лишь для совместимости со старыми приложениями. Поэтому в данной статье я буду основываться на CacheActiveX.dll.

    Об отличиях данных библиотек и нюансах, которые нужно учитывать при переходе на новую версию, можно почитать в Upgrading from CacheObject.dll.

    В каталоге выше можно найти и другие файлы, которые могут Вам пригодиться:
    • DelphiCallback.dll;
    • CacheList.ocx;
    • CacheQuery.ocx;
    • VISM.ocx.

    Импорт и установка компонент Caché Objects ActiveX в Delphi


    В данном разделе описывается установка основных классов и интерфейсов в среде Delphi для работы с объектами в СУБД Caché, используя раннее связывание. Для работы с поздним связыванием этот раздел можно пропустить.

    Итак, по порядку:
    1. выбираем пункт меню Component > Import Component...;

      image

      image

    2. выбираем для начала библиотеку типов CacheActiveX 2.0 Type Library;

      image

    3. задаём имя нашей закладки, куда мы хотим установить наши компоненты, а также другие параметры;

      image

    4. создаём наш модуль, пока без установки;

      image

    5. повторяем пункты 2-4 для следующих библиотек:
      • CacheActiveX 2.0 Type Library;
      • CacheList ActiveX Control module;
      • CacheQuery ActiveX Control module;
      • DelphiCallback 1.0 Type Library;
      • VisM 7.2 ActiveX Control;
      • TL 1.0 Type Library.
      Примечание: Создать модули можно и с помощью утилиты tlibimp.exe, входящей в поставку Delphi.

    6. создаём новый проект типа Package и добавляем в него все нами ранее созданные модули. Компилируем проект и инсталлируем наш пакет. Вот что в итоге у нас должно получиться:

      image

      image
      Примечание: Для более ранних версий Delphi процесс создания модулей отличается незначительно:
      • выбираем пункт меню Project > Import Type Library...;

        image

      • далее см. пункты выше.

    Генерация пользовательских прокси-классов


    В данном разделе описывается генерация пользовательских прокси-классов для работы с объектами в СУБД Caché, используя раннее связывание. Для работы с поздним связыванием этот раздел можно пропустить.

    Для начала необходимо сгенерировать ODL-файл, содержащий все необходимые нам пользовательские классы. Для этого следует воспользоваться утилитой odl_generator.exe, поставляемой с СУБД Caché.

    ПРИМЕР:
    odl_generator.exe -conn cn_iptcp:localhost[1972]:USER:_system:SYS -class-list test.txt -lib-name test -dir MIDL

    В данном примере утилита подключается к области USER, генерирует прокси-классы для классов перечисленных в файле test.txt, и сохраняет результат в файл MIDL\test.odl
    Внимание: Полученный таким образом файл предназначен для использования с библиотекой CacheActiveX.dll. Чтобы сгенерировать ODL-файл, предназначенный для работы со старой библиотекой, следует воспользоваться методом ExportODL класса %SYSTEM.OBJ

    ПРИМЕР:
      set list="%Library.Status,Sample.Person"
      
    do $system.OBJ.ExportODL(list,"c:\MIDL\test.odl","-d",.err)

    Итак, ODL-файл получен, теперь следует его скомпилировать, чтобы получить TLB-файл, а затем и PAS-файл. Для этого можно воспользоваться утилитами midl.exe или mktypelib.exe, входящими в комплект разработчика Visual C++.

    ПРИМЕР:
    midl /I . test.odl /tlb test.tlb
    tlibimp.exe -C- -P+ -Hr- -Ha- -Hs- -XM- test.tlb

    Примечание: Все вышеперечисленные этапы вы можете автоматизировать с помощью MAC-программы СУБД Caché.

    Подготовительные работы на сервере


    Создадим в нашей тестовой базе следующие классы данных:

    /// Встраиваемый класс
    Class pas.s Extends %SerialObject
    {

    /// Целое число (64-бит).
    Property
    aInteger As %Integer;

    /// Строка. Максимальная длина по умолчанию - 50 символов.
    Property
    aString As %String;

    }

    /// Вспомогательный хранимый класс.
    Class pas.a Extends %Persistent
    {

    /// Индекс на поле aA;
    Index
    aAIndex On aA;

    /// Отношение один-ко-многим, мощность "один". В SQL преобразуется в foreign key.
    Relationship
    aA As pas.test [ Cardinality = one, Inverse = aChilds ];

    Property aInteger As %Integer;

    Property aString As %String;

    }

    /// Подключение вспомогательного файла %occIO.inc с макросами.
    Include 
    %occIO

    /// Основной хранимый класс.
    Class pas.test Extends %Persistent
    {

    /// Задаём поля, возвращаемые при выборке всех экземпляров данного класса;
    Parameter 
    EXTENTQUERYSPEC As ROWSPEC [ Flags = LIST ] =  aBoolean,aInteger,aString,aDate,aTimeStamp";

    /// Булево значение (true/false/null);
    Property 
    aBoolean As %Boolean;

    Property aInteger As %Integer;

    Property aString As %String;

    /// Дата;
    Property 
    aDate As %Date;

    /// Дата+время;
    Property 
    aTimeStamp As %TimeStamp;

    /// Символьный поток (CLOB);
    Property 
    aMemo As %GlobalCharacterStream;

    /// Двоичный поток (BLOB);
    Property 
    aPhoto As %GlobalBinaryStream;

    /// Отношение один-ко-многим, мощность "много". В SQL отсутствует аналог.
    Relationship 
    aChilds As pas.a Cardinality = many, Inverse = aA ];

    /// Свойство-объект встраиваемого класса;
    ///<br>В SQL каждое свойство встраиваемого класса становится отдельным 
    ///<br>свойством класса-контейнера.
    Property 
    aS As pas.s;

    /// Коллекция-список строк;
    ///<br>В SQL это поле содержит значения, разделённые заданным разделителем, например запятой. 
    Property 
    aListOfString As list Of %String;

    /// Коллекция-список объектов вспомогательного класса;
    ///<br>В SQL это поле содержит значения первичных ключей объектов, разделённые заданным разделителем, например запятой. 
    Property 
    aListOfA As list Of pas.a;

    /// Коллекция-массив строк;
    ///<br>В SQL формируется виртуальная таблица. 
    Property 
    aArrOfString As array Of %String;

    /// Коллекция-массив объектов вспомогательного класса;
    ///<br>В SQL формируется виртуальная таблица. 
    Property 
    aArrOfA As array Of pas.a;

    /// Метод экземпляра класса.
    /// <br>Переопределяем встроенное событие, возникающее перед сохранением объекта.
    Method 
    %OnBeforeSave(insert As %BooleanAs %Status Private,ServerOnly = 1 ]
    {
      
    ; выводим имя класса и метода текущего контекста
      
    write "Hello from Cache! (",$$$CurrentClass,":",$$$CurrentMethod,")",!

      
    quit $$$OK
    }

    /// Запрос - хранимая процедура: демонстрация передачи простых типов данных.
    Query 
    test1(ABoolean As %BooleanAInteger As %IntegerAString As %String, ADate As %DateATimeStamp As %TimeStampAs %SQLQuery(CONTAINID 1ROWSPEC "ID:%String,aBoolean:%Boolean,aInteger:%Integer,aString:%String,aDate:%Date,aTimeStamp:%TimeStamp") [ SqlProc ]
    {
    SELECT %ID,aBoolean,aInteger,aString,aDate,aTimeStamp FROM pas.test WHERE
    (aBoolean=:ABoolean or :ABoolean is null)
    AND (
    aInteger=:AInteger or :AInteger is null)
    AND (
    aString=:AString or :AString is null)
    AND (
    aDate<:ADate or :ADate is null)
    AND (
    aTimeStamp<=:ATimeStamp or :ATimeStamp is null)
    }

    /// Запрос - хранимая процедура: демонстрация передачи параметра встроенного типа данных "список"
    ///<br>и использование его в запросе совместно с конструкцией %INLIST.
    Query 
    test2(AList As %ListAs %SQLQuery(CONTAINID 1ROWSPEC "ID:%String,aBoolean:%Boolean,aInteger:%Integer,aString:%String,aDate:%Date,aTimeStamp:%TimeStamp") [ SqlProc ]
    {
    SELECT %ID,aBoolean,aInteger,aString,aDate,aTimeStamp FROM pas.test WHERE ID %INLIST :AList
    }

    /// Метод класса.
    ClassMethod 
    test3(AList As %ListAs %Status
    {
      
    ; выводим на текущее устройство значение параметра AList
      
    write AList

      
    ; сохраняем значение в глобал
      
    set ^pastest=AList

      
    quit $$$OK
    }

    /// Демонстрация генерации ошибки.
    ClassMethod 
    test4() As %Status
    {
      
    quit $$$ERROR($$$GeneralError,"My error!")
    }

    /// Демонстрация передачи заранее неизвестного количества параметров.
    ClassMethod 
    test5(Arg1... As %ListAs %Status
    {
      
    ; выводим общее количество переданных параметров и их значения
      
    write "Invocation has ",$get(Arg1, 0)," element",$select(($get(Arg1, 0)=1):"", 1:"s"),!
      
    for i = 1 : 1 : $get(Arg1, 0)
      
    {
         
    write:($data(Arg1(i))>0) "Argument[",i,"]:",?15,$get(Arg1(i),"<NULL>"),!
      
    }
      
    quit $$$OK
    }

    /// Демонстрация передачи параметров более сложных типов: объекта нашего класса и потоков.
    /// <br>Также демонстрируется передача параметра по ссылке и выходных параметров.
    /// <br>Параметры:
    /// <br><var>ID</var> - строка;
    /// <br><var>A</var> - объект вспомогательного класса;
    /// <br><var>BLOB</var> - двоичный поток;
    /// <br><var>RS1</var> - символьный поток, содержащий данные запроса в формате Borland ® MyBase (DataSnap (TM)) XML DataSet;
    /// <br><var>RS2</var> - символьный поток, содержащий данные запроса в формате Borland ® MyBase (DataSnap (TM)) XML DataSet;
    ClassMethod 
    test6(
      
    ID As %String,
      
    ByRef As pas.a,
      
    Output BLOB As %BinaryStream,
      
    Output RS1 As %CharacterStream,
      
    Output RS2 As %CharacterStreamAs %Status
    {
      
    // меняем одно из свойств объекта, переданного по ссылке
      
    set A.aString=999
      
      
    // создаём объект двоичного потока
      
    set BLOB=##class(%GlobalBinaryStream).%New()

      
    // записываем в поток данные
      
    do BLOB.Write("123")
      
      
    // создаём объекты символьного потока
      
    set RS1=##class(%GlobalCharacterStream).%New()
      
    set RS2=##class(%GlobalCharacterStream).%New()
      
      
    // создаём объект пользовательского класса, позволяющего выгружать данные в формат XML для TClientDataSet
      
    set cds=##class(%XML.ZMyBaseDataSet).%New()

      
    // подготавливаем запрос
      
    do cds.Prepare("select * from pas.a where id %inlist ?")

      
    // передаём данные в запрос
      
    do cds.SetArgs($listbuild(1,2,3,9))

      
    // выгружаем данные в символьный поток в формате XML
       
    do cds.XMLExportToStream(.RS1)

      
    // закрываем (инициализируем заново) объект для выполнения другого запроса
      
    do cds.Close()
      
    do cds.Prepare("select ID,aBoolean,aInteger,aString,aDate,aTimeStamp from pas.test")
      
    do cds.XMLExportToStream(.RS2)
      
    do cds.Close()

      
    // возвращаем статус "Успешно"
      
    quit $$$OK
    }

    }

    Сгенерируем для них прокси-классы, а также для следующих классов:
    • %Library.ArrayOfDataTypes
    • %Library.ArrayOfObjects
    • %Library.ListOfObjects
    • %Library.ListOfDataTypes
    • %Library.RelationshipObject

    Теперь, если подключить к проекту сгенерированный PAS-файл, становятся доступны наиболее важные методы и свойства наших классов:

    image
    увеличить

    Внимание: Классы по работе с потоками и некоторые другие не следует импортировать из СУБД Caché, так как они уже зашиты в библиотеку CacheActiveX.dll и несовместимы со сгенерированными прокси-классами.

    Подключение к СУБД Caché


    Позднее связывание (старая библиотека):
    var _f:variant;
    begin
      _f:=CreateOleObject('CacheObject.Factory');
      if _f.Connect(_f.ConnectDlg('+%up')) then ShowMessage('OK') else ShowMessage('ERROR');

    Позднее связывание (новая библиотека):
    var _f:variant;
    begin
      _f:=CreateOleObject('CacheActiveX.Factory');
      if _f.Connect(_f.ConnectDlg('+%up')) then ShowMessage('OK') else ShowMessage('ERROR');

    Раннее связывание (новая библиотека):
    type
      Tfm = class(TForm)
        f: TFactory;
      ...
    begin
      ...
      if f.Connect1(f.ConnectDlg('+%up')) then ShowMessage('OK') else ShowMessage('ERROR');
      ...

    Внимание: Старая библиотека использует сервис %Service_CacheDirect и только неаутентифицированный доступ, новая — %Service_Bindings и другие методы доступа.

    Подробное описание методов класса TFactobry, а также других классов, можно найти в ActiveX API Reference

    Примеры вызова запросов и методов, используя раннее связывание


    Использование функциональности CallBack

    Для использования функциональности CallBack из Delphi следует воспользоваться готовым классом TCallback из файла DelphiCallback.dll.
    var f:TFactory;
        Callback1: TCallback;
        mm: TMemo;
      ...
      f.SetOutput(Callback1.OleObject);
      ...
    procedure Tfm.Callback1TextChanged(Sender: TObject; const p_bstrText: WideString);
    begin
      mm.Lines.Append(p_bstrText);
    end;

    Удаление всех данных

      mm.Lines.Text:='KillExtent'#10#13;
      // удаляем данные
      test_(f.Static('pas.test')).SYS_KillExtent(1);
      a(f.Static('pas.a')).SYS_KillExtent(1);

    Пример создания, заполнения, сохранения и освобождения объектов различных типов

    Про особенности закрытия объектов при работе через ActiveX можно почитать в статье Сергея Кудинова: Особенности закрытия объектов при работе через ActiveX, CPP-binding.

    Пример кода:
    ...
    uses test_TLB, AxCtrls, ComObj, ActiveX, Types;
    ...
    const
      N = 3;
    var
      i: integer;
      _t: test_;
      _a: A;
      _s: s;
      rel: RelationshipObject;
      listStr: ListOfDataTypes;
      listA: ListOfObjects;
      arrStr: ArrayOfDataTypes;
      arrA: ArrayOfObjects;
      stream: IDispatch;
    begin
    
      Screen.Cursor := crSQLWait;
      mm.Lines.Text:='Save'#10#13;
      try
        try
    
          // создаём новый объект класса pas.test
          _t := test_(f.New('pas.test'));
    
          // далее заполняем его данными
          _t.aBoolean := true;
          _t.aInteger := 50;
          _t.aString := 'Тестовая строка';
    
          // так мы можем присвоить null скалярному типу данных 
          // (%Integer,%Boolean,%Date и т.д.)
          Variant(_t).aDate := nil;
    
          _t.aDate := _t.aDateDisplayToLogical('02.03.2001');
          // или t.aDate:=StrToDate('02.03.2001');
          _t.aTimeStamp := _t.aTimeStampDisplayToLogical('1900-01-02 12:34:55');
          // или t.aTimeStamp:=StrToDateTime('02.01.1900 12:34:55');
    
          // заполняем данными символьный поток
          stream := _t.aMemo;
          ICharStream(stream).Write('Символьный поток');
          stream := nil;
    
          // заполняем данными бинарный поток
          stream := _t.aPhoto;
          IBinaryStream(stream).FileRead('C:\test.jpg');
          stream := nil;
    
          // создаём N "дочерних" объектов класса pas.a
          rel := RelationshipObject(_t.aChilds);
          for i := 1 to N do
          begin
            _a := A(f.New('pas.a'));
            _a.aInteger := i;
            _a.aString := 'rel' + IntToStr(i);
            rel.Insert(_a);
            _a.SYS_Close;
          end;
          _t.aChilds := rel;
          rel.SYS_Close;
    
          // заполняем данными встраиваемый объект
          _s := s(_t.aS_);
          _s.aInteger := 1;
          _s.aString := 's1';
          _s.SYS_Close;
    
          // заполняем данными список простых типов. В данном случае строк
          listStr := ListOfDataTypes(f.New('%ListOfDataTypes'));
          for i := 1 to N do
            listStr.Insert('str' + IntToStr(i));
          _t.aListOfString := listStr;
          listStr.SYS_Close;
    
          // создаём объекты класса pas.a и заполняем ими список
          listA := ListOfObjects(f.New('%ListOfObjects'));
          for i := 1 to N do
          begin
            _a := A(f.New('pas.a'));
            _a.aInteger := i;
            _a.aString := 'listA' + IntToStr(i);
            listA.Insert(_a);
            _a.SYS_Close;
          end;
          _t.aListOfA := listA;
          listA.SYS_Close;
    
          // заполняем данными массив простых типов. В данном случае строк
          arrStr := ArrayOfDataTypes(f.New('%ArrayOfDataTypes'));
          for i := 1 to N do
            arrStr.SetAt('astr' + IntToStr(i), 'arraykey' + IntToStr(i));
          _t.aArrOfString := arrStr;
          arrStr.SYS_Close;
    
          // создаём объекты класса pas.a и заполняем ими массив
          arrA := ArrayOfObjects(f.New('%ArrayOfObjects'));
          for i := 1 to N do
          begin
            _a := A(f.New('pas.a'));
            _a.aInteger := i;
            _a.aString := 'arrayA' + IntToStr(i);
            arrA.SetAt(_a, 'arraykey' + IntToStr(i));
            _a.SYS_Close;
          end;
          _t.aArrOfA := arrA;
          arrA.SYS_Close;
    
          // сохраняем наш объект в базу в рамках одной транзакции. До этого 
          // все изменения объекта производились в оперативной памяти на стороне 
          // клиента.
          _t.SYS_Save(0);
    
          // читаем данные бинарного потока для отображения на экране фотографии
          stream := _t.aPhoto;
          SetOlePicture(img.Picture, IBinaryStream(stream).GetPicture);
          stream := nil;
    
          // закрываем объект на сервере. Список незакрытых объектов вы можете 
          // просмотреть в Портале, меню "Процессы" (%sysOrefs).
          // Незакрытые объекты в дальнейшем чреваты неприятностями в виде 
          // утечек памяти на сервере и заблокированности данных.
          _t.SYS_Close;
          // закрываем объект на клиенте
          _t := nil;
    
          mm.Lines.Append('OK');
        except
          on E: Exception do
          begin
            mm.Lines.Append(E.Message);
          end;
        end;
      finally
        Screen.Cursor := crDefault;
        // принудительно синхронизируем состояние объектов на сервере, 
        // согласно их состоянию на клиенте
        f.ForceSync;
      end;

    Запрос Extent (выборка всех экземпляров хранимого класса)

    var mm: TMemo;
        rs: TResultSet;
      ...
      mm.Lines.Text:='Extent'#10#13;
      rs.ConnectTo(IResultSet(f.ResultSet('pas.test', 'Extent')));
      rs.Execute;
      while rs.Next do
      begin
        mm.Lines.Append(Format('ID = %s',[rs.GetDataAsString(1)]));
        mm.Lines.Append(Format('aBoolean = %s',[rs.Get('aBoolean')]));
        mm.Lines.Append(Format('aInteger = %s',[rs.Get('aInteger')]));
        mm.Lines.Append(Format('aString = %s',[rs.Get('aString')]));
        mm.Lines.Append(Format('aDate = %s',[rs.Get('aDate')]));
        mm.Lines.Append(Format('aTimeStamp = %s',[rs.Get('aTimeStamp')]));
        mm.Lines.Append('-----');
      end;
      rs.Close;
      rs.Disconnect;

    Запрос test1

    var
      i: integer;
    ...
      mm.Lines.Text:='test1'#10#13;
      rs.ConnectTo(IResultSet(f.ResultSet('pas.test', 'test1')));
      rs.SetParam(1, null);
      rs.SetParam(2, 50);
      rs.SetParam(3, null);
      rs.SetParam(4, '03.03.2001');
      rs.SetParam(5, '1900-01-02 12:34:55.0');
      rs.Execute;
      while rs.Next do
      begin
        for i := 1 to rs.GetColumnCount do
          mm.Lines.Append(rs.GetColumnName(i)+' = '+rs.GetDataAsString(i));
        mm.Lines.Append('-----');
      end;
      rs.Close;
      rs.Disconnect;

    Запрос test2

    var
      i: integer;
      syslist: TSyslist;
    ...
      mm.Lines.Text:='test2'#10#13;
    
      // заполняем объект класса %List
      syslist.Clear;
      syslist.Add(1);
      syslist.Add(2);
      syslist.Add(3);
    
      rs.ConnectTo(IResultSet(f.ResultSet('pas.test', 'test2')));
      rs.Execute(syslist.DefaultInterface);
      while rs.Next do
      begin
        for i := 1 to rs.GetColumnCount do
          mm.Lines.Append(rs.GetColumnName(i)+' = '+rs.GetDataAsString(i));
        mm.Lines.Append('-----');
      end;
      rs.Close;
      rs.Disconnect;

    Метод test3. Работа с объектом класса TSysList (тип %List в Caché)

      mm.Lines.Text:='test3'#10#13;
    
      // заполняем объект класса %List
      syslist.Clear;
      syslist.Add('16');
      syslist.Add('42');
      syslist.Add('35');
    
      test_(f.Static('pas.test')).test3(syslist.DefaultInterface);

    Метод test4. Обработка ошибок

      mm.Lines.Text:='test4'#10#13;
      try
        test_(f.Static('pas.test')).test4();
      except
        on E: Exception do
        begin
          mm.Lines.Append(E.Message);
        end;
      end;

    Метод test5

      mm.Lines.Text:='test5'#10#13;
    
      // заполняем объект класса %List
      syslist.Clear;
      syslist.Add('16');
      syslist.Add('42');
      syslist.Add('35');
    
      test_(f.Static('pas.test')).test5(syslist.DefaultInterface);

    Метод test6

    В данном примере используется класс %XML.ZMyBaseDataSet, который можно найти здесь. С его помощью можно на сервере формировать данные в формате Borland ® MyBase (DataSnap (TM)) XML DataSet. В том числе и для веб-сервисов.

    cds1,cds2:TClientDataSet;
    ...
    var _a:a;
        __a,blob,rs1,rs2:IDispatch;
        cs1,cs2:ICharStream;
    begin
      mm.Lines.Text:='test6'#10#13;
      try
        _a:=a(f.OpenId('pas.a','1'));
        __a:=_a;
        test_(f.Static('pas.test')).test6('1',__a,blob,rs1,rs2);
    
        cs1:=ICharStream(rs1);
        cs2:=ICharStream(rs2);
    
        mm.Lines.Append('A.aString = '+_a.aString);
    
        mm.Lines.Append('BLOB.Size = '+IntToStr(IBinaryStream(blob).size));
        mm.Lines.Append('RS1.Size = '+IntToStr(cs1.size));
        mm.Lines.Append('RS2.Size = '+IntToStr(cs2.size));
    
        cds1.XMLData:=cs1.Data;
        cds2.XMLData:=cs2.Data;
      finally
        _a.SYS_Close;
        _a:=nil;
        __a:=nil;
        blob:=nil;
        rs1:=nil;
        rs2:=nil;
        cs1:=nil;
        cs2:=nil;
        f.ForceSync;
      end;

    Для поддержки классом %XML.ZMyBaseDataSet данных в Unicode, а также других нереализованных типов данных Вам необходимо будет его доработать самостоятельно.

    Распространение приложения

    Для установки с приложением всех необходимых драйверов на новый компьютер следует скопировать все (для простоты. Набор нужных файлов можно ограничить) файлы из каталога C:\Program Files\Common Files\InterSystems\Cache\ и зарегистрировать в системе некоторые из них с помощью утилиты regsvr32.exe.

    ПРИМЕР:
    regsvr32.exe /s "C:\Program Files (x86)\Common Files\Intersystems\Cache\CacheQuery.ocx"
    regsvr32.exe /s "C:\Program Files (x86)\Common Files\Intersystems\Cache\CacheFormWizard.dll"
    regsvr32.exe /s "C:\Program Files (x86)\Common Files\Intersystems\Cache\CacheList.ocx"
    regsvr32.exe /s "C:\Program Files (x86)\Common Files\Intersystems\Cache\CacheActiveX.dll"
    regsvr32.exe /s "C:\Program Files (x86)\Common Files\Intersystems\Cache\vism.ocx"
    regsvr32.exe /s "C:\Program Files (x86)\Common Files\Intersystems\Cache\TL.dll"

    Исходники

    • серверная часть
      pas.xml
    • клиентская часть
      Unit1.dfm
      test_TLB.pas
      Unit1.pas
    • сгенерированные прокси-классы
      test.odl
    • +11
    • 9,5k
    • 5
    InterSystems
    76,00
    Вендор: СУБД Caché, OLAP DeepSee, шина Ensemble
    Поделиться публикацией

    Комментарии 5

      0
      Годная статья, но стоило бы «расшифровать» для неподготовленного читателя, что же это за зверь такой -Cache.
      Например, вот выдержка с сайта разработчика:

      InterSystems Caché – пост-реляционная объектная СУБД, предоставляющая неограниченные возможности для разработки Web-решений и клиент-серверных приложений.
      Caché предназначена для обработки транзакций в системах со сверхбольшими базами данных и практически неограниченным количеством одновременно работающих пользователей.

      Итого, можно использовать одну СУБД в энтерпрайзе и для нагруженного веб-сервера и для внутренних сервисов, например, для написания клиента управления контентом сайта (игры какой-нибудь) на Delphi.
        0
        Спасибо за замечание.

        Добавил примечание в начале статьи.
          0
          Расшифровку всё-таки убрал, т.к. она есть в профиле компании и писать её в каждой статье нецелесообразно.
          –4
          [lor-mode]Работа с ненужно на примере не нужно[/lor-mode]
            0
            [offtop]TL/DR, но картинка в начале поста — классная![/offtop]

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое