24.09.2018, 18:51 | #1 |
Участник
|
Приведение типов для таблиц ax2012
Всем привет.
Коллеги, ковыряюсь с наследованием табличек. Возможно ли приводить тип таблички к типу не определенному на этапе компиляции? Штатный способ X++: common as tableName Сделал вот такой способ X++: // приводит табличный буфер к заданному типу // работает аналогично оператору as // отличие в том, что 2-м операндом принимает переменную // т.е. на этапе компиляции тип может быть неизвестен public static Common as( Common _common, TableName _tableName) { anyType anyBuffer; Common ret; ; if (tableName2id(_tableName)) { // anyBuffer = _common as _tableName anyBuffer = new SysDictTable(tableName2id(_tableName)).makeRecord(); anyBuffer = _common; ret = anyBuffer; } return ret; } Но он немного хакерский. Есть что-то более документированное ? |
|
|
За это сообщение автора поблагодарили: sukhanchik (6). |
24.09.2018, 19:15 | #2 |
Участник
|
Если оно неопределенность на этапе компиляции, то это не привидение типов. В рантайме он уже своего собственного типа. Чего вы хотите добиться?
|
|
|
За это сообщение автора поблагодарили: Logger (1). |
25.09.2018, 11:28 | #3 |
Участник
|
Цитата:
Изначально я пытался порешать вот эту проблему: http://sashanazarov.blogspot.com/201...-fails-on.html Для решения написал метод в Global (он как раз решает проблему описанную в блоге sashanazarov - позволяет обойти баги ядра) X++: // обходим баг вызова orig() для табличек с наследованием // [url]http://sashanazarov.blogspot.com/2014/01/dynamics-ax-2012-orig-method-fails-on.html[/url] // [url=http://axforum.info/forums/showthread.php?p=328140#post328140]Опасный orig[/url] public static anyType origFieldValue_MRC(Common _common, FieldId _fieldId) { TableId tableId; TableId tableId4Field; FieldName fieldName; Common commonCasted; Common commonOrig; DictTable dictTable; anytype ret; ; if (_common.RecId == 0) { // обходим баг // [url=http://axforum.info/forums/showthread.php?p=328140#post328140]Опасный orig[/url] commonOrig = _common.orig(); commonOrig.doClear(); ret = commonOrig.(_fieldId); } else { dictTable = new DictTable(_common.TableId); if (!(dictTable && dictTable.supportInheritance())) { // обычная табличка без наследования - все как обычно commonOrig = _common.orig(); ret = commonOrig.(_fieldId); } else { // обходим баг // [url]http://sashanazarov.blogspot.com/2014/01/dynamics-ax-2012-orig-method-fails-on.html[/url] // решаем проблемы с получением значений из родительских табличек для orig() буфера // ищем tableId в иерархии наследования для которой первой определено поле _fieldId т.е. ту для которой оно было введено, а не отнаследовано. fieldName = fieldId2name(_common.TableId, _fieldId); tableId4Field = _common.TableId; if(fieldName) { tableId = dictTable.extends(); while(tableId) { dictTable = new DictTable(tableId); if (fieldName2id(tableId, fieldName)) { tableId4Field = tableId; } else { break; } tableId = dictTable.extends(); } } if (tableId4Field) { commonCasted = SysDictTable::as(_common, tableId2name(tableId4Field)); commonOrig = commonCasted.orig(); ret = commonOrig.(_fieldId); } else { // не должны сюда попасть. Можно кидать исключение. commonOrig = _common.orig(); ret = commonOrig.(_fieldId); } } } return ret; } иллюстрация работы метода origFieldValue_MRC - джоб: X++: // [url]http://sashanazarov.blogspot.com/2014/01/dynamics-ax-2012-orig-method-fails-on.html[/url] static void reproOrigBug_MRC(Args _args) { CompanyInfo companyInfo; DirPartyTable dirPartyTable; Common common5; Common common6; anytype any; ; select firstOnly companyInfo; info(strFmt("Поле СompanyInfo.DataAreaId (системное. Определено на СompanyInfo). Значение \"%1\"", companyInfo.DataAreaId)); info(strFmt("Поле СompanyInfo.VATNum (не наследовано). Значение \"%1\"", companyInfo.VATNum)); info(strFmt("Поле СompanyInfo.Name (наследовано из DirPartyTable - от головной таблички в иерархии наследования). Значение \"%1\"", companyInfo.Name)); info(strFmt("Поле СompanyInfo.PhoneticName (наследовано из DirOrganizationBase - от промежуточной таблички в иерархии наследования. 1-й уровень наследования). Значение \"%1\"", companyInfo.PhoneticName)); info(strFmt("Поле СompanyInfo.DEL_RelationTypeName_OMInternalOrg (наследовано из OMInternalOrganization - от промежуточной таблички в иерархии наследования. 2-й уровень наследования). Значение \"%1\"", companyInfo.DEL_RelationTypeName_OMInternalOrg)); info(""); info("Теперь проверяем работу Orig()"); info(""); info("1. Обычный вызов companyInfo.orig().FieldName - для полей из таблиц родителей - теряем значения"); info(strFmt("companyInfo.orig().DataAreaId = \"%1\"", companyInfo.orig().DataAreaId)); info(strFmt("companyInfo.orig().VATNum = \"%1\"", companyInfo.orig().VATNum)); info(strFmt("companyInfo.orig().Name = \"%1\" %2", companyInfo.orig().Name, (companyInfo.orig().Name ? "" : "Потеряли значение !"))); info(strFmt("companyInfo.orig().PhoneticName = \"%1\" %2", companyInfo.orig().PhoneticName, (companyInfo.orig().PhoneticName ? "" : "Потеряли значение !"))); info(strFmt("companyInfo.orig().DEL_RelationTypeName_OMInternalOrg = \"%1\" %2", companyInfo.orig().DEL_RelationTypeName_OMInternalOrg, (companyInfo.orig().DEL_RelationTypeName_OMInternalOrg ? "" : "Потеряли значение !"))); info(""); dirPartyTable = companyInfo as DirPartyTable; info("2. Явно приводим тип к табличной переменной другого типа. dirPartyTable = companyInfo as DirPartyTable. Вызов dirPartyTable.orig().FieldName. Лечит проблему но неудобно использовать. Надо помнить из какой таблички пришло поле в иерархии наследования. Держать в коде отдельную табличную переменную другого типа."); info(strFmt("dirPartyTable.orig().DataAreaId = \"%1\"", dirPartyTable.orig().DataAreaId)); info(strFmt("dirPartyTable.orig().VATNum - НЕПРИМЕНИМО" /*, dirPartyTable.orig().VATNum*/)); info(strFmt("dirPartyTable.orig().Name = \"%1\"", dirPartyTable.orig().Name)); info(strFmt("dirPartyTable.orig().PhoneticName - НЕПРИМЕНИМО" /*, dirPartyTable.orig().PhoneticName*/)); info(strFmt("dirPartyTable.orig().DEL_RelationTypeName_OMInternalOrg - НЕПРИМЕНИМО" /*, dirPartyTable.orig().DEL_RelationTypeName_OMInternalOrg*/)); info(""); info("3. Попытка достать через common.orig() и прочие танцы с бубном - не помогло"); common5 = null; common6 = null; common5 = new SysDictTable(tableNum(DirPartyTable)).makeRecord(); // тип DirPartyTable common5.data(companyInfo); // тип DirPartyTable , но это не помогает вытащить правильное значение common6 = common5.orig(); // тип DirPartyTable , но это не помогает вытащить правильное значение info(strFmt("common6.(fieldNum(DirPartyTable, Name)) = \"%1\"", common6.(fieldNum(DirPartyTable, Name)))); common5 = null; common6 = null; common5 = new SysDictTable(tableNum(DirPartyTable)).makeRecord(); // тип DirPartyTable , но это не помогает вытащить правильное значение common5.data(companyInfo.orig()); // вот тут уже при вызове Orig() значение потеряно common6 = common5; info(strFmt("common6.(fieldNum(DirPartyTable, Name)) = \"%1\"", common6.(fieldNum(DirPartyTable, Name)))); info(""); info("4. Игры с приведением типов через переменную с типом anyType - все хорошо, но неудобно использовать."); any = new SysDictTable(tableNum(DirPartyTable)).makeRecord(); // any - тип DirPartyTable any = companyInfo; // any - все равно тип DirPartyTable - в этот момент происходит "приведение" типа. common5 = any; // common5 - тип DirPartyTable common6 = common5.orig(); // common6 - тип DirPartyTable info(strFmt("common6.(fieldNum(DirPartyTable, Name)) = \"%1\"", common6.(fieldNum(DirPartyTable, Name)))); info(""); info("5. Достаем значение вызовом origFieldValue_MRC(companyInfo, ...) - все нормально. Удобно использовать."); info(strFmt("origFieldValue_MRC(companyInfo, fieldnum(CompanyInfo, DataAreaId)) = \"%1\"", origFieldValue_MRC(companyInfo, fieldnum(CompanyInfo, DataAreaId)))); info(strFmt("origFieldValue_MRC(companyInfo, fieldnum(CompanyInfo, VATNum)) = \"%1\"", origFieldValue_MRC(companyInfo, fieldnum(CompanyInfo, VATNum)))); info(strFmt("origFieldValue_MRC(companyInfo, fieldnum(CompanyInfo, Name)) = \"%1\"", origFieldValue_MRC(companyInfo, fieldnum(CompanyInfo, Name)))); info(strFmt("origFieldValue_MRC(companyInfo, fieldnum(CompanyInfo, PhoneticName)) = \"%1\"", origFieldValue_MRC(companyInfo, fieldnum(CompanyInfo, PhoneticName)))); info(strFmt("origFieldValue_MRC(companyInfo, fieldnum(CompanyInfo, DEL_RelationTypeName_OMInternalOrg)) = \"%1\"", origFieldValue_MRC(companyInfo, fieldnum(CompanyInfo, DEL_RelationTypeName_OMInternalOrg)))); info(""); } Последний раз редактировалось Logger; 25.09.2018 в 11:38. |
|
25.09.2018, 11:36 | #4 |
Участник
|
Вот еще интересный джоб
X++: static void reproOrigBug2_MRC(Args _args) { CompanyInfo companyInfo; Common common5; Common common6; anytype anytypeVar; ; common5 = null; common6 = null; select firstOnly companyInfo; common5 = companyInfo as DirPartyTable; // здесь отладчик покажет что common5 - тип companyInfo anytypeVar = new SysDictTable(tableNum(DirPartyTable)).makeRecord(); // здесь отладчик покажет что anytypeVar - тип DirPartyTable anytypeVar = companyInfo; // и здесь anytypeVar - остался с типом DirPartyTable хотя присваивали companyInfo - ну не может проинициализированная переменная anyType сменить тип. common6 = anytypeVar; // тоже тип DirPartyTable } Забыл написать - проверял все на DAX 2012 R3 CU13 Последний раз редактировалось Logger; 25.09.2018 в 11:42. |
|
25.09.2018, 11:41 | #5 |
Участник
|
Вообще, все что связано с наследованием таблиц - какое то дырявое.
Вот еще до кучи http://sysdictcoder.com/inconsistent...ysDictCoder%29 setTmp и иерархические таблицы |
|
25.09.2018, 13:48 | #6 |
Участник
|
Я в свое время столкнулся с тем, что buffer.TableId для таблиц, использующих наследование, зачастую возвращает идентификатор не совсем той таблицы, к которой относится буфер. Вместо этого обычно возвращается идентификатор одной из родительских таблиц, явно фигурирующих в коде во время компиляции - см. также А что такого принципиального в 2012? По моему хороший разработчик без труда в ней разберется Нужную информацию содержит поле buffer.RelationType, но тут есть нюанс: в интерпретаторе Х++ обращение к этому полю безопасно даже для Common, а вот при генерации CIL это приводит к ошибке компиляции. Более безопасным оказалось вызывать buffer.getInstanceRelationType() - он возвращает название конкретной таблицы-наследника либо пустую строку, если таблица не входит в иерархию наследования. В итоге для определения во время выполнения "настоящего" типа табличного буфера с учетом возможного наследования получился такой вспомогательный метод:
X++: // возвращает tableId таблицы либо, если таблица поддерживает наследование, то tableId конкретного "наследника" // safe тут относится к генерации кода CIL, которая валится на простом обращении к Common.RelationType public static TableId getTableIdOrReltaionTypeSafe(Common _record) { TableNameShort tableName = _record.getInstanceRelationType(); TableId ret = tableName2id(tableName); ; if (!ret) { ret = _record.TableId; } return ret; } |
|
|
За это сообщение автора поблагодарили: AlGol (3), raz (10), sukhanchik (8), Logger (8). |
25.09.2018, 16:23 | #7 |
Участник
|
Ха, а вот такой джоб
X++: static void reproOrigBug3_MRC(Args _args) { CompanyInfo companyInfo; DirPartyTable dirPartyTable; DirOrganizationBase dirOrganizationBase; Common common5; Common common5_orig; Common common6; Common common6_orig; anytype anytypeVar; ; common5 = null; common6 = null; select firstOnly companyInfo; info(strFmt("companyInfo.Name = %1", companyInfo.Name)); info(strFmt("companyInfo.orig().Name = %1", companyInfo.orig().Name)); dirOrganizationBase = companyInfo as DirOrganizationBase; info(strFmt("dirOrganizationBase.Name = %1", dirOrganizationBase.Name)); info(strFmt("dirOrganizationBase.orig().Name = %1", dirOrganizationBase.orig().Name)); dirPartyTable = companyInfo as DirPartyTable; info(strFmt("dirPartyTable.Name = %1", dirPartyTable.Name)); info(strFmt("dirPartyTable.orig().Name = %1", dirPartyTable.orig().Name)); common5 = companyInfo as DirPartyTable; // здесь отладчик покажет что common5 - тип companyInfo info(strFmt("common5.(fieldNum(dirPartyTable, Name)) = %1", common5.(fieldNum(dirPartyTable, Name)))); //info(strFmt("common5.orig().(fieldNum(dirPartyTable, Name)) = %1", common5.orig().(fieldNum(dirPartyTable, Name)))); // не компилируется common5_orig = common5.orig(); info(strFmt("common5_orig.(fieldNum(dirPartyTable, Name)) = %1", common5_orig.(fieldNum(dirPartyTable, Name)))); info(""); info("А так работает (с приведением типа)"); anytypeVar = new SysDictTable(tableNum(DirPartyTable)).makeRecord(); // здесь отладчик покажет что anytypeVar - тип DirPartyTable anytypeVar = companyInfo; // и здесь anytypeVar - остался с типом DirPartyTable хотя присваивали companyInfo - ну не может проинициализированная переменная anyType сменить тип. common6 = anytypeVar; // тоже тип DirPartyTable info(strFmt("common6.(fieldNum(dirPartyTable, Name)) = %1", common6.(fieldNum(dirPartyTable, Name)))); //info(strFmt("common6.orig().(fieldNum(dirPartyTable, Name)) = %1", common6.orig().(fieldNum(dirPartyTable, Name)))); // не компилируется common6_orig = common6.orig(); info(strFmt("common6_orig.(fieldNum(dirPartyTable, Name)) = %1", common6_orig.(fieldNum(dirPartyTable, Name)))); } Цитата:
companyInfo.Name = Company Dat
companyInfo.orig().Name = dirOrganizationBase.Name = Company Dat dirOrganizationBase.orig().Name = icName dirPartyTable.Name = Company Dat dirPartyTable.orig().Name = Company Dat common5.(fieldNum(dirPartyTable, Name)) = Company Dat common5_orig.(fieldNum(dirPartyTable, Name)) = А так работает (с приведением типа) common6.(fieldNum(dirPartyTable, Name)) = Company Dat common6_orig.(fieldNum(dirPartyTable, Name)) = Company Dat в моем случае в поле companyInfo.name лежит значение "Company Dat" если начитанный буфер привести к типу dirOrganizationBase то dirOrganizationBase.orig().Name вернет вообще мусор ! Будет возвращено значение "icName" вместо "Company Dat" !!! Последний раз редактировалось Logger; 25.09.2018 в 16:27. |
|
25.09.2018, 16:37 | #8 |
Участник
|
Тут может быть проблема в том, что Common ведет себя иногда как ссылочный тип, иногда как значение.
|
|
25.09.2018, 16:45 | #9 |
Участник
|
Цитата:
X++: // обходим баг вызова orig() для табличек с наследованием // [url=http://axforum.info/forums/showthread.php?p=412642#post412642]Приведение типов для таблиц ax2012[/url] // [url]http://sashanazarov.blogspot.com/2014/01/dynamics-ax-2012-orig-method-fails-on.html[/url] // [url=http://axforum.info/forums/showthread.php?p=328140#post328140]Опасный orig[/url] public static anyType origFieldValue_MRC(Common _common, FieldId _fieldId) { TableId tableId; TableId tableId4Field; FieldName fieldName; Common commonCasted; Common commonOrig; DictTable dictTable; anytype ret; ; if (_common.RecId == 0) { // обходим баг // [url=http://axforum.info/forums/showthread.php?p=328140#post328140]Опасный orig[/url] commonOrig = _common.orig(); commonOrig.doClear(); ret = commonOrig.(_fieldId); } else { dictTable = new DictTable(_common.TableId); if (!(dictTable && dictTable.supportInheritance())) { // обычная табличка без наследования - все как обычно commonOrig = _common.orig(); ret = commonOrig.(_fieldId); } else { // обходим баг // [url]http://sashanazarov.blogspot.com/2014/01/dynamics-ax-2012-orig-method-fails-on.html[/url] // решаем проблемы с получением значений из родительских табличек для orig() буфера dictTable = new DictTable(tableName2id(_common.getInstanceRelationType())); if (dictTable) { // ищем tableId в иерархии наследования для которой первой определено поле _fieldId т.е. ту для которой оно было введено, а не отнаследовано. fieldName = fieldId2name(_common.TableId, _fieldId); tableId4Field = _common.TableId; if(fieldName) { tableId = dictTable.extends(); while(tableId) { dictTable = new DictTable(tableId); if (fieldName2id(tableId, fieldName)) { tableId4Field = tableId; } else { break; } tableId = dictTable.extends(); } } } if (tableId4Field) { commonCasted = SysDictTable::as(_common, tableId2name(tableId4Field)); commonOrig = commonCasted.orig(); ret = commonOrig.(_fieldId); } else { // не должны сюда попасть. Можно кидать исключение. commonOrig = _common.orig(); ret = commonOrig.(_fieldId); } } } return ret; } |
|
25.09.2018, 16:55 | #10 |
Участник
|
Цитата:
И получается в иерархии DirPartyTable ... CompanyInfo у нас есть набор интерфейсов по работе с ядреным курсором. При этом реализация DirPartyTable.orig() и CompanyInfo.orig() разная. Каждый знает только про свой набор полей - про тот набор, который мы в AOT для таблички описали. И именно этот набор полей при вызове и заполняет в возвращаемом буфере. Соответственно, задача стояла как получить доступ к соответствующему интерфейсу курсора. Если в коде объявлена переменная соответствующего типа, то все просто, а если ее нет, то как ее получить в runTime. Ну, я нашел способ. P.S. Кстати, аналогичная проблема возникает при помещении и извлечении буфера в List (см. http://sashanazarov.blogspot.com/201...n-objects.html) а также при помещении и извлечении в контейнер таким способом, популярным в предыдущих версиях: X++: con = [buffer]; buffer = conPeek(con, 1); Видимо, во всех этих случаях задействован какой-то общий механизм в ядре. |
|
25.09.2018, 18:01 | #11 |
Молодой, подающий надежды
|
Цитата:
X++: SysDictTable::getConcreteTable(record); X++: /// <summary> /// Gets the ID for the concrete table for a specified table. /// </summary> /// <param name="_common"> /// The buffer of the given table whose concrete table must be found. /// </param> /// <returns> /// The table ID of the concrete table for the specified table. /// </returns> public static TableId getConcreteTable(Common _common) { DictTable dt=new DictTable(_common.TableId); TableId concrete=_common.TableId; str tablename; //Get concrete table for inheritance if(dt && dt.supportInheritance()) { tablename= _common.getInstanceRelationType(); if(tablename) { concrete=tableName2id(tablename); } } return concrete; }
__________________
Кононов Пётр Последний раз редактировалось pedrozzz; 25.09.2018 в 18:12. |
|
|
За это сообщение автора поблагодарили: sukhanchik (4), Logger (1), gl00mie (3). |
28.09.2018, 11:18 | #12 |
Участник
|
Я хотел было побрюзжать, что стандартный метод всегда создает экземпляр DictTable, но вспомнил тут одну особенность ядра: я некогда его ковырял (интересно же) и обнаружил, что экземпляры Dict-классов не каждый раз создаются и удаляются по мере работы кода X++, а создаются один раз при загрузке, затем ссылки на их эксземпляры сохраняются в массиве указателей, где индексом является идентификатор соотв. объекта. Возможно, для объектов приложения используется ленивая инициализация Dict-классов, но для объектов ядра (классов и таблиц) инициализация массива указателей происходит при загрузке ядра.
Таким образом, new DictTable() или new DictClass() транслируется ядром в обращение к массиву указателей и в общем случае отрабатывает очень быстро, без выделения памяти и последующей работы сборщика мусора. Поэтому Dict-классы - это очень быстрый API отражения, в отличие, скажем, от TreeNode. Что примечательно, SysDict-классы такой особенностью, насколько я знаю, не обладают и создаются, как любые другие объекты Х++, так что с точки зрения производительности с SysDict-классами стоит быть осторожнее. |
|
|
За это сообщение автора поблагодарили: sukhanchik (4), pedrozzz (3), skuull (2). |
28.09.2018, 11:37 | #13 |
Участник
|
Вот откуда растут ноги у такого агрессивного кеширования всего на свете в 2012-й...
|
|
28.09.2018, 12:39 | #14 |
Участник
|
То есть, если правильно понимаю, лучше не создавать SysDict*, а Dict* и потом приводить к SysDict*, если нужны методы именно Sys?
|
|
|
За это сообщение автора поблагодарили: Logger (1). |
01.03.2020, 19:55 | #15 |
Участник
|
Ха!
Оказывается методы isSql() и Name(dbBackend::Sql) класса DictIndex возвращают неправильное значение для для табличек из иерархии наследования. isSql() - всегда false Name(dbBackend::Sql) - всегда '' Да что же это такое! Почему реализация работы с наследованием в ядре такая кривая ? |
|
01.03.2020, 20:03 | #16 |
Участник
|
Фикс с исправлением для SysDictIndex во вложении.
|
|