INSTANCE (ЭКЗЕМПЛЯР)
Давайте на время отойдем от классов метасхемы АБС Бисквит и рассмотрим бизнес классы. Так например, рассмотрим, что такое платежный документ:
Как мы понимаем, платежный документ в АБС Бисквите состоит из набора классов метасхемы таких как: op (Документ), op-entry (Проводки), kau-entry (Субаналитические проводки) и op-bank (банк корреспондент).
Instance формирует объект платежный документ и представляет собой связанную совокупность динамических временных таблиц, каждая из которых определяет объекты соответствующих классов метасхемы АБС Бисквит, составляющих данный объект. Поля таких таблиц определяются набором реквизитов, как основных, так и дополнительных, соответствующего класса. При этом в качестве наименования поля берется не код реквизита из метасхемы, а его транслитерация. Связано это с тем, что наименование поля таблицы не может быть кириллическим, а код реквизита - может.
Просмотреть транслитерируемое значение кода реквизита можно на самом реквизите в метасхеме, оно отображается в правом верхнем углу формы просмотра/редактирования реквизита.
Транслитерируемое значение кода реквизита не хранится в базе данных, а определяется функцией GetMangledName из библиотеки для работы со строками и списками.
Для того чтобы нам, например, сформировать печатную форму интересующего нас документа, нам нужно сделать несколько запросов: получить все его проводки (таблица op-entry), получить реквизиты банка получателя/плательщика (op-bank), да и еще пробежаться по дополнительным реквизитам (таблицы signs и tmpsigns).
Instance позволяет получить весь объект (сразу оговорюсь еще раз, не как объект отдельного класса метасхемы АБС Бисквит, а как объект - платежный документ) в виде набора динамических временных таблиц, содержащих всю желаемую информацию по данному платежному документу.
Как же определяется, какие таблицы должны описывать наш объект, т.е. какие классы метасхемы АБС Бисквит задействованы в описании платежного документа? Структура нашего платежного документа определяется набором агрегируемых классов класса op, а так же их агрегируемых классов и т.д.
То, какие объекты агрегируемых классов попадают в соответствующие динамические таблицы Instance, определяется условиями агрегации заданными на реквизитах агрегации в метасхеме.
Что позволяет Instance:
- Получить полную информацию по объекту;
- Копировать объекты;
- Редактировать объекты;
- Создавать объекты;
- Удалять объекты.
Помимо реквизитов класса, таблицы Instance содержат ряд технических полей:
Поле | Тип данных | Описание |
__class | character | Класс метасхемы |
__table | character | Таблица БД |
__template | logical | Признак того, что запись содержит шаблон объекта |
__mode | int64 | Режим изменения записи |
__rowid | rowid | Ссылка на запись в БД |
__uptable | handle | Ссылка на агрегирующую таблицу |
__id | int64 | Идентификатор записи в наборе данных, нумерация начинается с 0 |
__upid | int64 | Ссылка на идентификатор в родительской таблице, нумерация начинается с 0 |
__tmpl-id | int64 | Ссылка на идентификатор шаблона транзакции |
__manual | int64 | В случае не нулевого значения запись не обрабатывается транзакцией при инициализации поля __template |
__private-data | character | Произвольные пользовательские данные |
__continue | logical | Служебное поле для пропуска ошибок обработки (продолжать обработку шаблона даже при ошибке) Применяется в методе поиска. Может применяться также в других методах, которые умеют использовать !-методы |
__prnt-cls-lst | character | Служебное поле для хранения списка родителей, для исключения зацикливания. Если добавляемый класс имеется в списке родителей, то он пропускается |
Индексы:
Индекс | Поля индекса | Описание |
__i__id | __id | |
__i__upid | __upid | |
__i__template | __template | |
__i__tmpl-id | __tmpl-id |
Описание атрибутов виджетов набора данных:
- TEMP-TABLE:ADM-DATA - содержит параметры создания набора данных, разделенных CHR(1): {&C_INST}~001<список агрегатов>~001<список пустых агрегатов>~001<список доп.реквизитов>~001<undo>;
- TEMP-TABLE:PRIVATE-DATA - содержит handle поля агрегирующей таблицы, через которое осуществляется агрегация;
- BUFFER:ADM-DATA - список из двух элементов разделенных CHR(1), в первом списке хранится список полей, являющихся агрегатами, во втором список полей, являющихся доп. реквизитами. ссылка на агрегируемую таблицу;
- BUFFER:PRIVATE-DATA - содержит handle буфера, связанного с таблицей БД для поддержки блокировок;
- FIELD:PRIVATE-DATA:
- для полей типа handle - ссылка на агрегируемую временную таблицу;
- для поля __id - последовательность для уникальной идентификации записей во временной таблице;
- для поля __template - значение YES указывает на то, что набор данных был закэширован и удален и может быть использован повторно;
- для полей доп. реквизитов - содержит константу FORM_DEF, означающую обязательность сохранения этого реквизита в БД.
- FIELD:VALIDATE-MESSAGE - содержит выражение WHERE, описывающее связь между агрегируемыми классами;
- FIELD:ADM-DATA - имеет значение YES, если наборы данных связаны отношением ИСПОЛЬЗОВАНИЕ;
- FIELD:LABEL и FIELD:COLUMN-LABEL - зарезервированы для пользователей
Работа с Instanсe осуществляется с помощью процедур и функций библиотеки для работы с отвязанным набором данных.
Перед формированием Instance возможно определить перечень реквизитов агрегации для которых будут сформированы соответствующие их классам динамические таблицы. Другими словами, возможно формирование не всех таблиц Instance, а только определенных. Задание необходимого состава Instance осуществляется с помощью процедуры PrepareInstance вызываемой перед его формированием.
К настройке Instance следует подходить очень разумно. Желательно указывать только те реквизиты агрегации, которые вы действительно планируете использовать. Посмотрим на класс loan (договора), у него достаточно большое количество агрегируемых реквизитов, у классов которых так же имеются свои реквизиты агрегации и т.д. Следовательно будет сформировано огромное количество таблиц, а это очень ресурсоемко и производительность будет не на высоте. При этом большинством из них вы даже не воспользуетесь. Стоит ли так впустую расходовать ресурсы системы? - думаю, что нет.
Просмотреть сформированный Instance можно с помощью процедуры instview.p
{globals.i}
{tmprecid.def}
{intrface.get data}
DEF VAR vClass AS CHAR NO-UNDO.
DEF VAR vSurr AS CHAR NO-UNDO.
DEF VAR mInstance AS HANDLE NO-UNDO.
DEF VAR mOk AS LOGICAL NO-UNDO.
FIND FIRST tmprecid NO-ERROR.
FIND FIRST op WHERE RECID(op) EQ tmprecid.id NO-LOCK NO-ERROR.
IF AVAIL op THEN
DO:
/* Определим класс и суррогат документа */
ASSIGN
vClass = op.class-code
vSurr = STRING(op.op)
.
/* Определим структуру Instance */
RUN PrepareInstance ("*").
/* Сформируем Instance отобранного документа */
RUN GetInstance IN h_data (vClass,
vSurr,
OUTPUT mInstance,
OUTPUT mOk).
/* Вызов вьювера набора данных */
IF mOk THEN
RUN instview.p(mInstance).
ELSE
DO:
{message "ОШИБКА Instance !"}.
END.
END.
/* Удаляем Instance */
IF VALID-HANDLE(mInstance)
THEN RUN DelEmptyInstance IN h_data (mInstance).
{intrface.del}
ПРИМЕРЫ
Получение значения реквизита основного класса Instance.
Получим значение реквизита name-send класса op.
{globals.i}
{tmprecid.def}
{intrface.get data}
DEF VAR vClass AS CHAR NO-UNDO.
DEF VAR vSurr AS CHAR NO-UNDO.
DEF VAR mInstance AS HANDLE NO-UNDO.
DEF VAR mTmpBuffer AS HANDLE NO-UNDO.
DEF VAR mOk AS LOGICAL NO-UNDO.
DEF VAR vValue AS CHAR NO-UNDO.
FIND FIRST tmprecid NO-ERROR.
FIND FIRST op WHERE RECID(op) EQ tmprecid.id NO-LOCK NO-ERROR.
IF AVAIL op THEN
DO:
/* Определим класс и суррогат документа */
ASSIGN
vClass = op.class-code
vSurr = STRING(op.op)
.
/* Определим структуру Instance */
RUN PrepareInstance ("*").
/* Сформируем Instance отобранного документа */
RUN GetInstance IN h_data(vClass,
vSurr,
OUTPUT mInstance,
OUTPUT mOk).
/* Получим необходимое нам значение */
IF mOk THEN
DO:
mTmpBuffer = mInstance:DEFAULT-BUFFER-HANDLE
mTmpBuffer:FIND-FIRST().
vValue = mTmpBuffer:BUFFER-FIELD("name-send"):BUFFER-VALUE NO-ERROR.
END.
ELSE
DO:
{message "ОШИБКА Instance !" }.
END.
END.
{setdest.i}
PUT UNFORMATTED vValue FORMAT "x(50)" SKIP.
{preview.i}
/* Удаляем Instance */
IF VALID-HANDLE(mInstance)
THEN RUN DelEmptyInstance IN h_data (mInstance).
{intrface.del}
Получение значение реквизита агрегируемого класса Instance.
Получим значение счета кредита 2-ой проводки многопроводочного документа (реквизит acct-cr). На классе op заведено 4 реквизита для агрегируемого класса op-entry: op-entry1, op-entry2, op-entry3 и op-entry.
Для реквизита op-entry с условием агрегации of op при формировании Instance формируется динамическая таблица содержащая все проводки документа. Для реквизитов op-entry1, op-entry2 и op-entry3 формируются таблицы содержащие по одной записи соответствующей проводки документа.
Таким образом, у нас есть два варианта получения счета кредита: из таблицы для реквизита op-entry2 и из таблицы для реквизита op-entry, но в этом случае нам необходимо будет задать условие поиска записи с op-entry = 2.
{globals.i}
{tmprecid.def}
{intrface.get data}
DEF VAR vClass AS CHAR NO-UNDO.
DEF VAR vSurr AS CHAR NO-UNDO.
DEF VAR mInstance AS HANDLE NO-UNDO.
DEF VAR mH AS HANDLE NO-UNDO.
DEF VAR mTmpBuffer AS HANDLE NO-UNDO.
DEF VAR mOk AS LOGICAL NO-UNDO.
DEF VAR vValue AS CHAR NO-UNDO.
FIND FIRST tmprecid NO-ERROR.
FIND FIRST op WHERE RECID(op) EQ tmprecid.id NO-LOCK NO-ERROR.
IF AVAIL op THEN
DO:
/* Определим класс и суррогат документа */
ASSIGN
vClass = op.class-code
vSurr = STRING(op.op)
.
/* Определим структуру Instance */
RUN PrepareInstance ("op-entry2").
/* Сформируем Instance отобранного документа */
RUN GetInstance IN h_data (vClass,
vSurr,
OUTPUT mInstance,
OUTPUT mOk).
/* Получим необходмиое нам значение */
IF mOk THEN
DO:
mTmpBuffer = mInstance:DEFAULT-BUFFER-HANDLE.
mTmpBuffer:FIND-FIRST().
/* определяем handle для таблицы реквизита op-entry */
mH = mTmpBuffer:BUFFER-FIELD("op-entry"):BUFFER-VALUE NO-ERROR.
/* работаем с таблицей агрегируемого класса op-entry */
mTmpBuffer = mH:DEFAULT-BUFFER-HANDLE.
mTmpBuffer:FIND-FIRST("WHERE op-entry = 2").
vValue = mTmpBuffer:BUFFER-FIELD("acct-cr"):BUFFER-VALUE NO-ERROR.
END.
ELSE
DO:
{message "ОШИБКА Instance !" }.
END.
END.
{setdest.i}
PUT UNFORMATTED vValue FORMAT "x(50)" SKIP.
{preview.i}
/* Удаляем Instance */
IF VALID-HANDLE(mInstance)
THEN RUN DelEmptyInstance IN h_data (mInstance).
{intrface.del}
Удаление объекта.
Удаление объекта из базы данных посредством Instance осуществляется с помощью процедуры DelInstance, в качестве параметра которой необходимо передать handle Instace удаляемого объекта. Таким образом первым делом необходимо сформировать Instance этого объекта. Далее процедура последовательно удаляет все объекты в базе данных входящих в состав сформированного Instance начиная с главного класса.
Для примера, рассмотрим удаление платежного документа. Тут нас ждет одна проблема. Главным классом для Instance будет являться класс op и следовательно запись именно этого класса будет удаляться первой. На таблице op определен триггер на событие DELETE, который не позволяет удалить запись op первой, так как у документа имеются балансовые проводки (op-entry) и требует сначала удалить их, а потом уже и сам документ (op). Но процедура DelInstance делает все наоборот. Поэтому, для удаления документа нам придется отключить триггер.
Отключить триггер можно воспользовавшись инклюд-файлом dis-trig.i, которому в качестве аргументов необходимо передать таблицу и событие для которого необходимо отключить триггер.
Далее проблема заключается в том, что триггер не только проверяет наличие балансовых проводок по удаляемому документу, а делает еще и другие проверки, которые мы тем самым проигнорируем.
{globals.i}
{tmprecid.def}
{dis-trig.i
"op" "delete"
}
{intrface.get data}
DEF VAR vClass AS CHAR NO-UNDO.
DEF VAR vSurr AS CHAR NO-UNDO.
DEF VAR mInstance AS HANDLE NO-UNDO.
DEF VAR mOk AS LOGICAL NO-UNDO.
FIND FIRST tmprecid NO-ERROR.
FIND FIRST op WHERE RECID(op) EQ tmprecid.id NO-LOCK NO-ERROR.
IF AVAIL op THEN
DO:
/* Определим класс и суррогат */
ASSIGN
vClass = op.class-code
vSurr = STRING(op.op).
.
/* Сформируем Instance отобранного документа */
RUN PrepareInstance ("*").
RUN GetInstance IN h_data (vClass,
vSurr,
OUTPUT mInstance,
OUTPUT mOk).
/* Удаляем документ */
IF mOk THEN
RUN DelInstance IN h_data (vClass,
mInstance,
YES,
OUTPUT mOk).
IF mOk THEN
DO:
{message "Объект удален!"}
END.
ELSE
DO:
{message "ОШИБКА удаления объекта!"}
END.
END.
{intrface.del}
Удаление экземпляра объекта так же возможно с помощью процедуры SetInstance. Для этого, предварительно, режим изменения необходимо установить в значение {&MOD_DELETE}.
{globals.i}
{tmprecid.def}
{form.def}
{dis-trig.i
"op" "delete"
}
{intrface.get data}
DEF VAR vClass AS CHAR NO-UNDO.
DEF VAR vSurr AS CHAR NO-UNDO.
DEF VAR mInstance AS HANDLE NO-UNDO.
DEF VAR mOk AS LOGICAL NO-UNDO.
FIND FIRST tmprecid NO-ERROR.
FIND FIRST op WHERE RECID(op) EQ tmprecid.id NO-ERROR.
IF AVAIL OP THEN
DO:
/* Определим класс и суррогат */
ASSIGN
vClass = op.class-code
vSurr = STRING(op.op).
.
/* Сформируем Instance отобранного документа */
RUN PrepareInstance ("*").
RUN GetInstance IN h_data (vClass,
vSurr,
OUTPUT mInstance,
OUTPUT mOk).
/* Установим режим - удаление */
RUN SetInstanceProp(mInstance,"__mode","{&MOD_DELETE}",OUTPUT mOk).
/* Удаляем объект */
IF mOk THEN
RUN SetInstance IN h_data (vClass,
mInstance,
OUTPUT mOk).
IF mOk THEN
DO:
{message "Объект удален!"}.
END.
ELSE
DO:
{message "ОШИБКА удаления объекта!"}.
END.
END.
{intrface.del}
Экспорт Instance в XML
Для экспорта Instance в XML предварительно соберем все таблицы составляющие его в DATASET, после чего осуществим сам экспорт с помощью метода WRITE-XML. При этом, перед формированием самого Instance необходимо определить глобальную переменную gInstanceXml = YES, которая указывает на необходимость замены спец. символов при транслитерации реквизитов в функции GetMangledName.
{globals.i}
{tmprecid.def}
{intrface.get data}
DEF NEW GLOBAL SHARED VAR gInstanceXml AS LOG NO-UNDO.
DEF VAR vClass AS CHAR NO-UNDO.
DEF VAR vSurr AS CHAR NO-UNDO.
DEF VAR hInstance AS HANDLE NO-UNDO.
DEF VAR hAggr AS HANDLE NO-UNDO.
DEF VAR hField AS HANDLE NO-UNDO.
DEF VAR hBuff AS HANDLE NO-UNDO.
DEF VAR hDSet AS HANDLE NO-UNDO.
DEF VAR mOk AS LOGICAL NO-UNDO.
DEF VAR vAggrFields AS CHAR NO-UNDO.
DEF VAR vNumFields AS INT64 NO-UNDO.
DEF VAR vCnt AS INT64 NO-UNDO.
gInstanceXml = YES.
FIND FIRST tmprecid NO-ERROR.
FIND FIRST op WHERE RECID(op) EQ tmprecid.id NO-LOCK NO-ERROR.
IF AVAIL op THEN
DO:
/* Определим класс и суррогат документа */
ASSIGN
vClass = op.class-code
vSurr = STRING(op.op)
.
/* Определим структуру Instance */
RUN PrepareInstance ("op-entry,op-bank").
/* Сформируем Instance отобранного документа */
RUN GetInstance IN h_data (vClass,
vSurr,
OUTPUT hInstance,
OUTPUT mOk).
IF mOk THEN
DO:
hBuff = hInstance:DEFAULT-BUFFER-HANDLE.
/* Список агригируемых реквизитов */
vAggrFields = ENTRY(1,hBuff:ADM-DATA,CHR(1)).
vNumFields = NUM-ENTRIES(vAggrFields).
/* Создаем DATASET */
CREATE DATASET hDSet.
hDSet:ADD-BUFFER(hBuff).
/* Проходим по всем агригируемым реквизитам */
DO vCnt = 1 TO vNumFields:
hField = hBuff:BUFFER-FIELD(ENTRY(vCnt,vAggrFields)).
hAggr = hField:BUFFER-VALUE.
IF VALID-HANDLE(hAggr) THEN
hDSet:ADD-BUFFER(hAggr:DEFAULT-BUFFER-HANDLE).
END.
END.
hDSet:WRITE-XML("FILE","op.xml",FALSE,"windows-1251").
END.
/* Удаляем DATASET */
IF VALID-HANDLE(hDSet)
THEN DELETE OBJECT hDSet.
/* Удаляем Instance */
IF VALID-HANDLE(hInstance)
THEN RUN DelEmptyInstance IN h_data (hInstance).
gInstanceXml = NO.
{intrface.del del}
Данная процедура осуществляет выгрузку отмеченного в браузере документов платежного документа в файл op.xml.
В заключение хотелось бы сказать, что на использовании Instance построен механизм универсальных транзакций и метод Form класса метасхемы.