RunBase Framework в MS DAX 2012
Платформа RunBase обеспечивает стандартизированный подход к созданию процессов и пакетных заданий в Microsoft Dynamics AX. Фреймворк должен использоваться для каждой функции наподобие задания в приложении.
Contents
Описание
Среда RunBase использует инфраструктуру Dialog для запроса ввода данных (параметров) пользователем. Параметры являются переменными-членами класса и добавляются в диалоговое окно. Тип элемента управления, который используется в диалоговом окне, зависит от расширенного типа данных, который используется при добавлении элементов управления в диалоговое окно. Класс также использует платформу SysLastValue для сохранения данных об использовании и платформу Operation Progress для отображения хода выполнения операции.
Каждый диалоговый элемент управления имеет соответствующую переменную класса, которая используется для установки и получения значений из диалоговых элементов управления и для сохранения значений для использования позже, когда задание выполняется пакетным сервером.
RunBase
Предположим, что необходимо получить список заказов на продажу определенного клиента за некоторый промежуток времени и произвести над ним какие-либо действия.
classDeclaration
Переменные класса добавляются в локальный макрос в виде «списка» переменных, чтобы их значения могли сохраняться средой SysLastValue. Список переменных имеет соответствующий номер версии для отслеживания различных версий списков, которые могут существовать. Номер версии обычно начинается с единицы и увеличивается каждый раз, когда изменяется число переменных в списке. Метод распаковки должен быть соответствующим образом изменен, чтобы обеспечить правильное назначение значений переменным и предотвратить возникновение ошибок трассировки стека, когда размер постоянных переменных не соответствует размеру локального списка макросов.
class RunBase_Example extends RunBase { CustAccount custAccount; FromDate fromDate; ToDate toDate; DialogField dfCustAccount; DialogField dfFromDate; DialogField dfToDate; #define.CurrentVersion(1) //Текущая версия данных #define.Version1(1) //Первая версия #localmacro.CurrentList //Список переменных custAccount, fromDate, toDate #endmacro }
dialog
Вызывается из метода prompt() и возвращает класс запуска формы dialog, formRun. Элементы управления добавляются в диалоговое окно, а значения элементов управления задаются значениями соответствующих им переменных класса.
protected Object dialog() { DialogRunbase dialog; dialog = super(); dfCustAccount = dialog.addFieldValue(typeId(CustAccount), custAccount); dfFromDate = dialog.addFieldValue(typeId(FromDate), fromDate); dfToDate = dialog.addFieldValue(typeId(ToDate), toDate); return dialog; }
getFromDialog
Значения переменных класса назначаются значениями элементов управления, которые были добавлены в диалоговое окно.
public boolean getFromDialog() { boolean ret; ret = super(); custAccount = dfCustAccount.value(); fromDate = dfFromDate.value(); toDate = dfToDate.value(); return ret; }
initParmDefault
Вызывается, когда метод unPack возвращает false, т.е. данные об использовании не найдены. Переменные класса, которые находятся в списке макросов, должны быть инициализированы в методе initParmDefault.
public void initParmDefault() { super(); fromDate = systemDateGet(); toDate = systemDateGet(); }
new
Создает новый экземпляр класса. Вызывается при выполнении в пакете. Всегда вызывайте super() в новом методе. Новый метод не должен быть слишком медленным (он периодически вызывается для административных целей). Поскольку классы, наследуемые от RunBase, могут выполняться в журнале обработки пакетов, новый метод не должен иметь никаких аргументов. Причина этого заключается в том, что типы переменных объекта могут вызывать ошибки трассировки стека, когда система перечисляет все дочерние классы RunBase в процессе настройки пакетных журналов.
public void new() { super(); }
pack
Переменные класса сохраняются с помощью локального макроса. Вызывается, когда диалоговое окно закрывается нажатием «ОК».
public container pack() { return [#CurrentVersion, #CurrentList]; }
run
Основной метод класса. Так же вызывается при пакетной обрабоке.
Если задание выполняет операцию, которая занимает значительное время, рекомендуется отобразить диалоговое окно хода выполнения. Платформа RunBase использует платформу Operation Progress для отображения хода выполнения операции. Чтобы отобразить текущий прогресс, необходимо добавить код в метод run() класса.
public void run() { #OCCRetryCount if (! this.validate()) throw error(""); try { this.progressInit(); ttsBegin; // Обнуление переменных, измененных в транзакции ... // Выполнение задания while (queryRun.next()) { progress.incCount(); progress.setText(); salesTable = queryRun.get(tableNum(SalesTable)); ... ... ttsCommit; } catch (Exception::Deadlock) { retry; } catch (Exception::UpdateConflict) { if (appl.ttsLevel() == 0) { if (xSession::currentRetryCount() >= #RetryNum) { throw Exception::UpdateConflictNotRecovered; } else { retry; } } else { throw Exception::UpdateConflict; } } }
unpack
Переменные класса в локальном макросе восстанавливаются. Метод распаковки вызывается перед запуском. Так же вызывается при выполнении в пакете.
public boolean unpack(container _packedClass) { Version version = RunBase::getVersion(_packedClass); switch (version) { case #CurrentVersion: [version, #CurrentList] = _packedClass; break; default: return false; } return true; }
description
Возвращает описание (базового) класса и будет отображаться как описание в списке пакетов, и определяет заголовок диалогового окна. Метод описания должен иметь свойство RunAs, установленное в Called или эквивалентное поведение. Если класс определен как клиент или сервер, нужно определить метод как клиент-сервер.
public static ClassDescription description() { return "Description"; }
construct
public static RunBase_Example construct() { return new RunBase_Example(); }
main
По умолчанию класс не является «исполняемым», то есть его нельзя открыть из главного меню или из формы с использованием пункта меню. Чтобы сделать класс «исполняемым», он должен иметь статический метод main (точку входа) с одним параметром Args. Когда класс выполняется, ядро вызывает статический метод main класса и передает ему объект Args. Основной метод вызывается интерактивно и не вызывается пакетным сервером. Объект Args содержит свойства, которые могут использоваться классом. С помощью пункта меню доступ к классу (заданию) можно получить из главного меню или из формы. Важно всегда устанавливать ключ безопасности новых пунктов меню.
public static void main(Args _args) { RunBase_Example runBase = RunBase_Example::construct(); if (runBase.prompt()) { runBase.run(); } }
RunBaseBatch
Класс RunBaseBatch расширяет класс RunBase и позволяет создавать классы (задания), которые могут быть добавлены в очередь пакета. Как только задание будет добавлено в очередь, оно будет выполнено пакетным сервером. Эта концепция известна как пакетная обработка.
Внимание! Чтение и запись файлов обычно происходит на уровне клиента. Поскольку задание выполняется пакетным сервером, используемый путь должен быть доступен для пакетного сервера или AOS в зависимости от того, на каком уровне выполняется задание. Ошибки трассировки стека будут возникать, если используется путь на уровне клиента, поскольку пакетный сервер и AOS не могут получить доступ к файловой системе клиента.
canGoBatch
Возвращает true если возможна пакетная обработка (т.е. можно указать когда запускать обработку и как часто ее запускать в автоматическом режиме). Возвращает false если пакетная обработка не возможна. Указывает, может ли класс выполняться в пакетном режиме. Каркас добавляет вкладку «Пакет» в диалоговое окно, если этот метод возвращает true. По умолчанию возвращает true.
public boolean canGoBatch() { return true; }
showQueryValues
Класс RunBaseBatch поддерживает использование запроса для выбора базовых данных. Если класс использует запрос, а метод queryRun переопределяется, среда RunBase автоматически отображает диапазоны запросов и их значения в диалоговом окне. Нажав кнопку «Выбрать», пользователь может изменить запрос, установив или удалив значения запроса или добавив новые диапазоны. Можно расширить запрос, добавив отношения (объединения) к связанным таблицам и, наконец, изменив порядок сортировки.
public boolean showQueryValues() { return true; }
queryRun
Метод возвращает QueryRun.
public SysQueryRun queryRun() { return queryRun; }
initQuery
В методе initQuery() происходит инициализация запроса.
private Query initQuery() { Query query = super(); QueryBuildRange qbr; qbr = query.dataSourceTable(tableNum(SalesTable)).findRange(fieldNum(SalesTable, CustAccount)); if (!qbr) { qbr = query.dataSourceTable(tableNum(SalesTable)).addRange(fieldNum(SalesTable, CustAccount)); } qbr.value(custAccount); qbr = query.dataSourceTable(tableNum(SalesTable)).findRange(fieldNum(SalesTable, SalesId)); if (!qbr) { qbr = query.dataSourceTable(tableNum(SalesTable)).addRange(fieldNum(SalesTable, SalesId)); } qbr.value(salesIdFilter); qbr = query.dataSourceTable(tableNum(SalesTable)).findRange(fieldNum(SalesTable, ShippingDateRequested)); if (!qbr) { qbr = query.dataSourceTable(tableNum(SalesTable)).addRange(fieldNum(SalesTable, ShippingDateRequested)); } qbr.value(SysQuery::range(fromDate, toDate)); return query; }
initQueryRun
public void initQueryRun() { queryRun = new QueryRun(this.initQuery()); }
getFromDialog
public boolean getFromDialog() { boolean ret; ret = super(); custAccount = dfCustAccount.value(); fromDate = dfFromDate.value(); toDate = dfToDate.value(); this.initQueryRun(); // Добавим инициализацию queryRun return ret; }
RunBaseReport
Класс RunBaseReport является частью RunBase Framework. Его основная цель — предоставить пользовательский интерфейс до запуска отчета и упаковать бизнес-логику. Класс RunBaseReport решает проблему отсутствия наследования для отчетов в Dynamics AX. Но Dynamics AX поддерживает наследование для классов.
lastValueElementName
Метод LastValueElementName() возвращает имя отчета, который должен быть запущен. Фреймворк использует запрос из отчета
public identifiername lastValueElementName() { return reportstr(MyReport); }
pack
Переопределение метода pack().
public container pack() { return [#CurrentVersion, #CurrentList] + [super()]; }
unpack
Переопределение метода unpack().
public boolean unpack(container packedClass) { Version version = RunBase::getVersion(packedClass); container base; boolean ret; switch (version) { case #CurrentVersion: [version, #CurrentList, base] = packedClass; ret = super(base); break; default: ret = false; break; } return ret; }
Перекрытие методов полей диалога
Предположим, что на форму дилога добавили два поля: useFilter(checkBox) и salesIdFilter(string).
Далее понадобятся названия добавленных элементов. Для того, чтобы узнать имя элемента управления, необходимо запустить диалог и в контекстном меню необходимиого элемента выберать параметры. Но рекомендуется при добавлении полей задавать им произвольное имя.
Внимание! После добавления новых переменных в список #CurrentList необходимо изменить текущую версию.
class RunBase_Example extends RunBase { CustAccount custAccount; TransDate fromDate; TransDate toDate; // --> NoYes useFilter; SalesIdFilter salesIdFilter; //Новый расширенный тип данных // <-- DialogField dfCustAccount; DialogField dfFromDate; DialogField dfToDate; // --> DialogField dfUseFilter; DialogField dfSalesIdFilter; DialogRunbase dialog; // <-- #define.CurrentVersion(2) //Текущая версия данных #define.Version1(1) //Первая версия #localmacro.CurrentList //Список переменных custAccount, fromDate, toDate, // --> useFilter, salesIdFilter // <-- #endmacro }
Далее необходимо добавить поля в диалог.
protected void dialog() { ... // --> dialog.customFieldName('dfUseFilter'); dfUseFilter = dialog.addFieldValue(typeId(NoYes), useFilter, 'Фильтр по номеру заказа'); dialog.customFieldName('dfSalesIdFilter'); dfSalesIdFilter = dialog.addFieldValue(typeId(SalesIdFilter), salesIdFilter); dfSalesIdFilter.enabled(dfUseFilter.value() == NoYes::Yes); //Отключаем поле по умолчанию dfSalesIdFilter.replaceOnLookup(false); //Выключает замещение текста, так чтобы можно было выбирать несколько значений dialog.allowControlMethodOverload(true); // <-- }
Чтобы получить введенное пользователем значение, изменим метод getFromDialog()
public boolean getFromDialog() { boolean ret; ; ret = super(); custAccount = dfCustAccount.value(); fromDate = dfFromDate.value(); toDate = dfToDate.value(); // --> useFilter = dfUseFilter.value(); salesIdFilter = dfSalesIdFilter.value(); // <-- return ret; }
lookup
Ниже представлен один из вариантов использования перекрытия метода lookup() элемента управления.
protected void dfSalesIdFilter_lookup() { SysTableLookup sysTableLookup = SysTableLookup::newParameters(tableNum(SalesTable), dfSalesIdFilter.control()); Query query = new Query(); QueryBuildDatasource queryBuildDatasource = query.addDataSource(tableNum(SalesTable)); ; dialog.dialogOnClient().updateServer(); if (custAccount) { queryBuildDatasource.addRange(fieldNum(SalesTable, OrderAccount)).value(custAccount); } sysTableLookup.addLookupfield(fieldNum(SalesTable, SalesId)); sysTableLookup.addLookupfield(fieldNum(SalesTable, CustAccount)); sysTableLookup.addLookupfield(fieldNum(SalesTable, ShippingDateRequested)); sysTableLookup.parmQuery(query); sysTableLookup.performFormLookup(); }
modified
Ниже представлен один из вариантов использования перекрытия метода modified() элемента управления.
protected boolean dialogMachineryDeploy_modified() { boolean ret = dfUseFilter.control().modified(); ; dialog.dialogOnClient().updateServer(); dfSalesIdFilter.enabled(dfUseFilter.value() == NoYes::Yes); dfSalesIdFilter.value(''); dialog.updateClient(dialog.dialogOnClient()); return ret; }
Примечания
См. также
Comments
So empty here ... leave a comment!