<?xml version="1.0" encoding="UTF-8"?>

<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
	<channel>
		<title>AXForum - Блоги - Трудности перехода. Автор gl00mie</title>
		<link>//axforum.info/forums/blog.php?u=5390</link>
		<description>Microsoft Dynamics: Axapta, CRM, Navision. Форум, Вопросы и помощь специалистов.</description>
		<language>ru</language>
		<lastBuildDate>Fri, 24 Apr 2026 17:50:47 GMT</lastBuildDate>
		<generator>vBulletin</generator>
		<ttl>15</ttl>
		<image>
			<url>http://m.axforum.info//img.axforum.info/misc/rss.jpg</url>
			<title>AXForum - Блоги - Трудности перехода. Автор gl00mie</title>
			<link>//axforum.info/forums/blog.php?u=5390</link>
		</image>
		<item>
			<title>Поиск источника SQL-запросов в коде X++ методом пересечения множеств перекрестных ссылок</title>
			<link>//axforum.info/forums/blog.php?b=8329</link>
			<pubDate>Mon, 07 Nov 2022 11:52:04 GMT</pubDate>
			<description><![CDATA[Допустим, со стороны СУБД вы (или ваши DBA) поймали какой-то SQL-запрос из X++, который вам нужно оптимизировать - именно в коде X++, а не просто пришпилить plan guide. Как найти источник запроса в коде приложения? Можно включить трассировку "длинных" SQL-запросов для всех подряд пользователей и потом периодически шерстить логи. Но, во-первых, не факт, что ваш запрос выполняется долго и попадет в лог (а логировать быстрые часто выполняемые запросы может выйти себе дороже). Во-вторых, нет гарантии, что запрос будет повторно выполняться вскоре после включения трассировки. И в-третьих, само по себе включение трассировки SQL-запросов для всех пользователей в рабочей системе может потребовать выполнения определеннных бюрократических процедур.

При таких вводных может подойти вариант поиска, основанный на пересечении множеств перекрестных ссылок на поля таблиц в запросе. В чем-то это похоже на определение местоположения точки за счет триангуляции.

Вложение 438 (//axforum.info/forums/attachment.php?attachmentid=438)

Возьмем для примера такой SQL-запрос (в чуть причесанном виде):

PHP:
---------
SELECT
  T1.REFERENCEDISTRIBUTION,
  T4.EXCHANGERATE1,
  T4.EXCHANGERATE2,
  T4.REPORTINGEXCHANGERATE1,
  T4.REPORTINGEXCHANGERATE2
FROM
  tempdb."DBO".t100007_18B95DDA3C21488C85AC09C9FEB59FE5 T1
  CROSS JOIN ACCOUNTINGDISTRIBUTION T2
  CROSS JOIN SUBLEDGERJOURNALACCOUNTENTRYDISTRIBUTION T3
  CROSS JOIN SUBLEDGERJOURNALACCOUNTENTRY T4
WHERE ((T1.PARTITION = @P1) AND (T1.REFERENCEDISTRIBUTION <> @P2))
  AND ((T2.PARTITION = @P3) AND (T2.RECID = T1.REFERENCEDISTRIBUTION))
  AND ((T3.PARTITION = @P4) AND (T3.ACCOUNTINGDISTRIBUTION = T1.REFERENCEDISTRIBUTION))
  AND ((T4.PARTITION = @P5) AND (T4.RECID = T3.SUBLEDGERJOURNALACCOUNTENTRY))
GROUP BY
  T1.REFERENCEDISTRIBUTION,
  T4.EXCHANGERATE1,
  T4.EXCHANGERATE2,
  T4.REPORTINGEXCHANGERATE1,
  T4.REPORTINGEXCHANGERATE2
ORDER BY
  T1.REFERENCEDISTRIBUTION,
  T4.EXCHANGERATE1,
  T4.EXCHANGERATE2,
  T4.REPORTINGEXCHANGERATE1,
  T4.REPORTINGEXCHANGERATE2
---------
Здесь t100007_* - это экземпляр временной таблицы AccountingDistributionTmpJournalize. В запросе упоминаются поля в условиях WHERE (AccountingDistributionTmpJournalize.ReferenceDistribution, SubledgerJournalAccountEntryDistribution.SubledgerJournalAccountEntry, SubledgerJournalAccountEntryDistribution.AccountingDistribution), а также поля в списке выбора SELECT и группировки GROUP BY (SubledgerJournalAccountEntry.ExchangeRate1, SubledgerJournalAccountEntry.ReportingExchRate1, etc). Обратим внимание, что в запросе нет агрегирования, следовательно, все перекрестные ссылки на поля будут с типом Read (для DAX2012 и более ранних версий, которые это различают). Если бы в запросе использовалось агрегирование значений полей (что-то вроде sum(PurchQty) или maxOf(RecId)), то соотв. ссылки были бы типа Write - это в общем случае позволило бы дополнительно сузить выборку.

Далее описан способ локализации источника запроса по перекрестным ссылкам с помощью Excel. Весьма вероятно, что кто-то уже давно так делает. Также наверняка это всё можно худо-бедно автоматизировать в среде разработки и исключить Excel из инструментария.

Найдем перекрестные ссылки на перечисленные поля таблиц и выгрузим их по отдельности на листы одной книги Excel. Для простоты будем рассматривать только ссылки из методов, игнорируя ссылки из Query, View, Form и прочих подобных объектов - с Query и View разговор отдельный... Для этого ссыки можно отфильтровать по наличию номера строки (1..) и дополнительно при желании - по типу доступа (Read/Write). Тип доступа может быть особенно интересен, если запрос использует агрегирование. После выгрузки можно удалить столбцы "Строка" и "Столбец", оставив только "Путь" и "Ссылка" (Read/Write), а затем с помощью Excel удалить дубликаты (Данные/Работа с данными/Удалить дубликаты, если кто еще не пользуется). На выходе мы получим для каждого интересующего нас поля в SQL-запросе отдельный лист Excel с таблицей уникальных путей к методам, где это поле используется. В зависимостти от "везения" ссылок на каждое поле может быть от полудюжины до нескольких сотен. Если выгружать перекрестные ссылки штатно через Ctrl-T, то данные будут отформатированы как таблицы, что весьма удобно. Если же это не так, то стоит заняться форматированием самостоятельно (в Excel Главная/Стили/Форматировать как таблицу) - так вы получите именованные таблицы в книге и именованные колонки в таблицах вместо безымянных ссылок на ячейки.

На одном из листов добавим столбцы для каждого поля из соседних листов. В колонке нужно использовать формулу, которая позволит понять, есть ли пути из ссылок на текущем листе среди путей, ссылающихся на соотв. дополнительное поле. Я обычно использую формулу наподобие такой:

Код:
---------
=ЕСЛИОШИБКА(ПОИСКПОЗ([@Путь];Table2[[#Все];[Путь]];0)>0;"")
---------
Для тех, кто по каким-то причинам нечасто работает в Excel с данными, отформатированными как таблицы, поясню. Здесь [@Путь] - это ссылка на ячейку в той же строке текущей таблицы и в колонке "Путь"; Table2[[#Все];[Путь]] - ссылка на колонку (одномерный массив ячеек) на соседнем листе, где данные отформатированы в виде именованной таблицы Table2, причем из таблицы тоже берется колонка "Путь". Т.е. мы ищем путь из перекрестных ссылок в текущей строке таблицы среди ссылок в другой таблице, относящихся к другому полю. Если такой путь найдется, то его индекс будет больше нуля, и мы увидим значение "ИСТИНА", если же не найдется, то функция ЕСЛИОШИБКА() подставит пустую строку.

Вот как может выглядеть результат такого пересечения множеств перекрестных ссылок:
Вложение 439 (//axforum.info/forums/attachment.php?attachmentid=439)

Отфильтровав все колонки с формулами по значению "ИСТИНА", мы останемся с тремя ссылками:
* \Classes\SubledgerJournalizer\loadaccountingDistributionTmp
* \Classes\SubledgerJournalizer\PSALoadaccountingDistReleaseTmp
* \Classes\SubledgerJournalizer\loadReferenceDistributionInformation

После этого методом пристального взгляда нетрудно установить, что в двух методах поля в действительности лишь "читаются", а вот SQL-запрос выполняет из \Classes\SubledgerJournalizer\loadReferenceDistributionInformation.
Вложение 440 (//axforum.info/forums/attachment.php?attachmentid=440)

Описанный способ, разумеется, - не панацея, и встречаются разного рода нюансы. Скажем, в коде X++ работа может вестись с Map-ами, а не непосредственно с таблицами, тогда перекрестные ссылки нужно будет искать на поля Map-ов. Также SQL-запрос может формироваться на основе заранее созданного Query и лишь дополнительно фильтроваться в коде, впрочем, из моего скромного опыта, это встречается существенно реже.]]></description>
			<content:encoded><![CDATA[<div>Допустим, со стороны СУБД вы (или ваши DBA) поймали какой-то SQL-запрос из X++, который вам нужно оптимизировать - именно в коде X++, а не просто пришпилить plan guide. Как найти источник запроса в коде приложения? Можно включить трассировку &quot;длинных&quot; SQL-запросов для всех подряд пользователей и потом периодически шерстить логи. Но, во-первых, не факт, что ваш запрос выполняется долго и попадет в лог (а логировать быстрые часто выполняемые запросы может выйти себе дороже). Во-вторых, нет гарантии, что запрос будет повторно выполняться вскоре после включения трассировки. И в-третьих, само по себе включение трассировки SQL-запросов для всех пользователей в рабочей системе может потребовать выполнения определеннных бюрократических процедур.<br />
<br />
При таких вводных может подойти вариант поиска, основанный на пересечении множеств перекрестных ссылок на поля таблиц в запросе. В чем-то это похоже на определение местоположения точки за счет триангуляции.<br />
<br />
<img src="//axforum.info/forums/blog_attachment.php?attachmentid=438&amp;d=1667821157" border="0" alt="Название: xrefs-triangulation-picture.jpg
Просмотров: 70792

Размер: 41.9 Кб" style="margin: 2px" /><br />
<br />
Возьмем для примера такой SQL-запрос (в чуть причесанном виде):<br />
<div style="margin:20px; margin-top:5px">
	<div class="smallfont" style="margin-bottom:2px">PHP код:</div>
	<div class="alt2" dir="ltr" style="
		margin: 0px;
		padding: 6px;
		border: 1px inset;
		width: 640px;
		height: 450px;
		text-align: left;
		overflow: auto">
		<code style="white-space:nowrap">
			<!-- php buffer start --><code><span style="color: #000000">
<span style="color: #0000BB">SELECT<br />&nbsp;&nbsp;T1</span><span style="color: #007700">.</span><span style="color: #0000BB">REFERENCEDISTRIBUTION</span><span style="color: #007700">,<br />&nbsp;&nbsp;</span><span style="color: #0000BB">T4</span><span style="color: #007700">.</span><span style="color: #0000BB">EXCHANGERATE1</span><span style="color: #007700">,<br />&nbsp;&nbsp;</span><span style="color: #0000BB">T4</span><span style="color: #007700">.</span><span style="color: #0000BB">EXCHANGERATE2</span><span style="color: #007700">,<br />&nbsp;&nbsp;</span><span style="color: #0000BB">T4</span><span style="color: #007700">.</span><span style="color: #0000BB">REPORTINGEXCHANGERATE1</span><span style="color: #007700">,<br />&nbsp;&nbsp;</span><span style="color: #0000BB">T4</span><span style="color: #007700">.</span><span style="color: #0000BB">REPORTINGEXCHANGERATE2<br />FROM<br />&nbsp;&nbsp;tempdb</span><span style="color: #007700">.</span><span style="color: #DD0000">"DBO"</span><span style="color: #007700">.</span><span style="color: #0000BB">t100007_18B95DDA3C21488C85AC09C9FEB59FE5&nbsp;T1<br />&nbsp;&nbsp;CROSS&nbsp;JOIN&nbsp;ACCOUNTINGDISTRIBUTION&nbsp;T2<br />&nbsp;&nbsp;CROSS&nbsp;JOIN&nbsp;SUBLEDGERJOURNALACCOUNTENTRYDISTRIBUTION&nbsp;T3<br />&nbsp;&nbsp;CROSS&nbsp;JOIN&nbsp;SUBLEDGERJOURNALACCOUNTENTRY&nbsp;T4<br />WHERE&nbsp;</span><span style="color: #007700">((</span><span style="color: #0000BB">T1</span><span style="color: #007700">.</span><span style="color: #0000BB">PARTITION&nbsp;</span><span style="color: #007700">=&nbsp;@</span><span style="color: #0000BB">P1</span><span style="color: #007700">)&nbsp;AND&nbsp;(</span><span style="color: #0000BB">T1</span><span style="color: #007700">.</span><span style="color: #0000BB">REFERENCEDISTRIBUTION&nbsp;</span><span style="color: #007700">&lt;&gt;&nbsp;@</span><span style="color: #0000BB">P2</span><span style="color: #007700">))<br />&nbsp;&nbsp;AND&nbsp;((</span><span style="color: #0000BB">T2</span><span style="color: #007700">.</span><span style="color: #0000BB">PARTITION&nbsp;</span><span style="color: #007700">=&nbsp;@</span><span style="color: #0000BB">P3</span><span style="color: #007700">)&nbsp;AND&nbsp;(</span><span style="color: #0000BB">T2</span><span style="color: #007700">.</span><span style="color: #0000BB">RECID&nbsp;</span><span style="color: #007700">=&nbsp;</span><span style="color: #0000BB">T1</span><span style="color: #007700">.</span><span style="color: #0000BB">REFERENCEDISTRIBUTION</span><span style="color: #007700">))<br />&nbsp;&nbsp;AND&nbsp;((</span><span style="color: #0000BB">T3</span><span style="color: #007700">.</span><span style="color: #0000BB">PARTITION&nbsp;</span><span style="color: #007700">=&nbsp;@</span><span style="color: #0000BB">P4</span><span style="color: #007700">)&nbsp;AND&nbsp;(</span><span style="color: #0000BB">T3</span><span style="color: #007700">.</span><span style="color: #0000BB">ACCOUNTINGDISTRIBUTION&nbsp;</span><span style="color: #007700">=&nbsp;</span><span style="color: #0000BB">T1</span><span style="color: #007700">.</span><span style="color: #0000BB">REFERENCEDISTRIBUTION</span><span style="color: #007700">))<br />&nbsp;&nbsp;AND&nbsp;((</span><span style="color: #0000BB">T4</span><span style="color: #007700">.</span><span style="color: #0000BB">PARTITION&nbsp;</span><span style="color: #007700">=&nbsp;@</span><span style="color: #0000BB">P5</span><span style="color: #007700">)&nbsp;AND&nbsp;(</span><span style="color: #0000BB">T4</span><span style="color: #007700">.</span><span style="color: #0000BB">RECID&nbsp;</span><span style="color: #007700">=&nbsp;</span><span style="color: #0000BB">T3</span><span style="color: #007700">.</span><span style="color: #0000BB">SUBLEDGERJOURNALACCOUNTENTRY</span><span style="color: #007700">))<br /></span><span style="color: #0000BB">GROUP&nbsp;BY<br />&nbsp;&nbsp;T1</span><span style="color: #007700">.</span><span style="color: #0000BB">REFERENCEDISTRIBUTION</span><span style="color: #007700">,<br />&nbsp;&nbsp;</span><span style="color: #0000BB">T4</span><span style="color: #007700">.</span><span style="color: #0000BB">EXCHANGERATE1</span><span style="color: #007700">,<br />&nbsp;&nbsp;</span><span style="color: #0000BB">T4</span><span style="color: #007700">.</span><span style="color: #0000BB">EXCHANGERATE2</span><span style="color: #007700">,<br />&nbsp;&nbsp;</span><span style="color: #0000BB">T4</span><span style="color: #007700">.</span><span style="color: #0000BB">REPORTINGEXCHANGERATE1</span><span style="color: #007700">,<br />&nbsp;&nbsp;</span><span style="color: #0000BB">T4</span><span style="color: #007700">.</span><span style="color: #0000BB">REPORTINGEXCHANGERATE2<br />ORDER&nbsp;BY<br />&nbsp;&nbsp;T1</span><span style="color: #007700">.</span><span style="color: #0000BB">REFERENCEDISTRIBUTION</span><span style="color: #007700">,<br />&nbsp;&nbsp;</span><span style="color: #0000BB">T4</span><span style="color: #007700">.</span><span style="color: #0000BB">EXCHANGERATE1</span><span style="color: #007700">,<br />&nbsp;&nbsp;</span><span style="color: #0000BB">T4</span><span style="color: #007700">.</span><span style="color: #0000BB">EXCHANGERATE2</span><span style="color: #007700">,<br />&nbsp;&nbsp;</span><span style="color: #0000BB">T4</span><span style="color: #007700">.</span><span style="color: #0000BB">REPORTINGEXCHANGERATE1</span><span style="color: #007700">,<br />&nbsp;&nbsp;</span><span style="color: #0000BB">T4</span><span style="color: #007700">.</span><span style="color: #0000BB">REPORTINGEXCHANGERATE2&nbsp;<br /></span>
</span>
</code><!-- php buffer end -->
		</code>
	</div>
</div>Здесь t100007_* - это экземпляр временной таблицы AccountingDistributionTmpJournalize. В запросе упоминаются поля в условиях WHERE (AccountingDistributionTmpJournalize.ReferenceDistribution, SubledgerJournalAccountEntryDistribution.SubledgerJournalAccountEntry, SubledgerJournalAccountEntryDistribution.AccountingDistribution), а также поля в списке выбора SELECT и группировки GROUP BY (SubledgerJournalAccountEntry.ExchangeRate1, SubledgerJournalAccountEntry.ReportingExchRate1, etc). Обратим внимание, что в запросе нет агрегирования, следовательно, все перекрестные ссылки на поля будут с типом Read (для DAX2012 и более ранних версий, которые это различают). Если бы в запросе использовалось агрегирование значений полей (что-то вроде sum(PurchQty) или maxOf(RecId)), то соотв. ссылки были бы типа Write - это в общем случае позволило бы дополнительно сузить выборку.<br />
<br />
Далее описан способ локализации источника запроса по перекрестным ссылкам с помощью Excel. Весьма вероятно, что кто-то уже давно так делает. Также наверняка это всё можно худо-бедно автоматизировать в среде разработки и исключить Excel из инструментария.<br />
<br />
Найдем перекрестные ссылки на перечисленные поля таблиц и выгрузим их по отдельности на листы одной книги Excel. Для простоты будем рассматривать только ссылки из методов, игнорируя ссылки из Query, View, Form и прочих подобных объектов - с Query и View разговор отдельный... Для этого ссыки можно отфильтровать по наличию номера строки (1..) и дополнительно при желании - по типу доступа (Read/Write). Тип доступа может быть особенно интересен, если запрос использует агрегирование. После выгрузки можно удалить столбцы &quot;Строка&quot; и &quot;Столбец&quot;, оставив только &quot;Путь&quot; и &quot;Ссылка&quot; (Read/Write), а затем с помощью Excel удалить дубликаты (Данные/Работа с данными/Удалить дубликаты, если кто еще не пользуется). На выходе мы получим для каждого интересующего нас поля в SQL-запросе отдельный лист Excel с таблицей уникальных путей к методам, где это поле используется. В зависимостти от &quot;везения&quot; ссылок на каждое поле может быть от полудюжины до нескольких сотен. Если выгружать перекрестные ссылки штатно через Ctrl-T, то данные будут отформатированы как таблицы, что весьма удобно. Если же это не так, то стоит заняться форматированием самостоятельно (в Excel Главная/Стили/Форматировать как таблицу) - так вы получите именованные таблицы в книге и именованные колонки в таблицах вместо безымянных ссылок на ячейки.<br />
<br />
На одном из листов добавим столбцы для каждого поля из соседних листов. В колонке нужно использовать формулу, которая позволит понять, есть ли пути из ссылок на текущем листе среди путей, ссылающихся на соотв. дополнительное поле. Я обычно использую формулу наподобие такой:<br />
<div class="xpp"><div class="smallfont xpp_title">Код:</div><pre class="alt2 xpp_code">=ЕСЛИОШИБКА(ПОИСКПОЗ([@Путь];Table2[[#Все];[Путь]];0)&gt;0;&quot;&quot;)</pre></div>Для тех, кто по каким-то причинам нечасто работает в Excel с данными, отформатированными как таблицы, поясню. Здесь [@Путь] - это ссылка на ячейку в той же строке текущей таблицы и в колонке &quot;Путь&quot;; Table2[[#Все];[Путь]] - ссылка на колонку (одномерный массив ячеек) на соседнем листе, где данные отформатированы в виде именованной таблицы Table2, причем из таблицы тоже берется колонка &quot;Путь&quot;. Т.е. мы ищем путь из перекрестных ссылок в текущей строке таблицы среди ссылок в другой таблице, относящихся к другому полю. Если такой путь найдется, то его индекс будет больше нуля, и мы увидим значение &quot;ИСТИНА&quot;, если же не найдется, то функция ЕСЛИОШИБКА() подставит пустую строку.<br />
<br />
Вот как может выглядеть результат такого пересечения множеств перекрестных ссылок:<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=439&amp;d=1667821157" rel="Lightbox" id="attachment439" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=439&amp;thumb=1&amp;d=1667821157" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: xrefs-triangulation-table.png
Просмотров: 14573
Размер:	64.6 Кб
ID:	439" style="margin: 2px" /></a><br />
<br />
Отфильтровав все колонки с формулами по значению &quot;ИСТИНА&quot;, мы останемся с тремя ссылками:<ul><li>\Classes\SubledgerJournalizer\loadaccountingDistributionTmp</li>
<li>\Classes\SubledgerJournalizer\PSALoadaccountingDistReleaseTmp</li>
<li>\Classes\SubledgerJournalizer\loadReferenceDistributionInformation</li>
</ul>После этого методом пристального взгляда нетрудно установить, что в двух методах поля в действительности лишь &quot;читаются&quot;, а вот SQL-запрос выполняет из \Classes\SubledgerJournalizer\loadReferenceDistributionInformation.<br />
<a href="//axforum.info/forums/blog_attachment.php?attachmentid=440&amp;d=1667821182" rel="Lightbox" id="attachment440" ><img src="//axforum.info/forums/blog_attachment.php?attachmentid=440&amp;thumb=1&amp;d=1667821182" class="thumbnail" border="0" alt="Нажмите на изображение для увеличения
Название: xrefs-triangulation-result.png
Просмотров: 14568
Размер:	125.5 Кб
ID:	440" style="margin: 2px" /></a><br />
<br />
Описанный способ, разумеется, - не панацея, и встречаются разного рода нюансы. Скажем, в коде X++ работа может вестись с Map-ами, а не непосредственно с таблицами, тогда перекрестные ссылки нужно будет искать на поля Map-ов. Также SQL-запрос может формироваться на основе заранее созданного Query и лишь дополнительно фильтроваться в коде, впрочем, из моего скромного опыта, это встречается существенно реже.</div>

]]></content:encoded>
			<dc:creator>gl00mie</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8329</guid>
		</item>
		<item>
			<title>Типы X++ date/utcDateTime НЕ преобразуются автоматически в System.DateTime в .NET CIL</title>
			<link>//axforum.info/forums/blog.php?b=8169</link>
			<pubDate>Fri, 16 Jan 2015 21:32:39 GMT</pubDate>
			<description><![CDATA[Замечательная статья MSDN How to: Marshal Between X++ and CLR Primitive Types [AX 2012] (http://msdn.microsoft.com/EN-US/library/cc584291.aspx) утверждает следующее:
---Цитата---
In Microsoft Dynamics AX, the X++ language does implicit conversion or marshaling between several X++ primitive types and their counterpart types managed by the common language runtime (CLR). This means that the X++ assignment operator, the single equal sign (=), can be used between certain pairings of an X++ type with a .NET Framework CLR type.
---Конец цитаты---
Среди поддерживаемых значится преобразование *date* ↔ System.DateTime (http://msdn.microsoft.com/EN-US/library/cc584291.aspx#SysDttm1d); на самом деле, в интерпретаторе X++, по крайней мере, в AX 2012 R2 и выше замечательно преобразуются и значения типов *utcDateTime* ↔ System.DateTime (http://msdn.microsoft.com/EN-US/library/cc584291.aspx#SysDttm1d)! Но рассмотрим такой пример кода:System.Web.HttpCookie   cookie  = new System.Web.HttpCookie('test');
date                    dateVar = today() + 1;
cookie.set_Expires(dateVar);Всё хорошо, пока дело не доходит до выполнения в CIL - а тут нас поджидает System.NotSupportedException с сообщением вида
---Цитата---
DateTimeConverter cannot convert from Microsoft.Dynamics.Ax.Xpp.AxShared.Date
---Конец цитаты---
 либо
---Цитата---
DateTimeConverter cannot convert from Microsoft.Dynamics.Ax.Xpp.AxShared.utcdatetime
---Конец цитаты---
если rvalue - типа utcDateTime. В чем же дело? А в том, что магия неявного преобразования типов работает только в интерпретаторе байт-кода X++, а в CIL все происходит иначе, поэтому если есть хотя бы нановероятность того, что подобный ваш код будет выполняться в CIL, всегда выполняйте *явное преобразование типов* вида:cookie.set_Expires(CLRInterop::getObjectForAnyType(dateVar));]]></description>
			<content:encoded><![CDATA[<div>Замечательная статья MSDN <a href="http://msdn.microsoft.com/EN-US/library/cc584291.aspx" target="_blank">How to: Marshal Between X++ and CLR Primitive Types [AX 2012]</a> утверждает следующее:<div class="q">
	<div class="smallfont q_title">Цитата:</div>
	<div class="alt2 q_body">
		
			In Microsoft Dynamics AX, the X++ language does implicit conversion or marshaling between several X++ primitive types and their counterpart types managed by the common language runtime (CLR). This means that the X++ assignment operator, the single equal sign (=), can be used between certain pairings of an X++ type with a .NET Framework CLR type.
		
	</div>
</div>Среди поддерживаемых значится преобразование <b>date</b> ↔ <a href="http://msdn.microsoft.com/EN-US/library/cc584291.aspx#SysDttm1d" target="_blank">System.DateTime</a>; на самом деле, в интерпретаторе X++, по крайней мере, в AX 2012 R2 и выше замечательно преобразуются и значения типов <b>utcDateTime</b> ↔ <a href="http://msdn.microsoft.com/EN-US/library/cc584291.aspx#SysDttm1d" target="_blank">System.DateTime</a>! Но рассмотрим такой пример кода:<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">System.Web.HttpCookie   cookie  = <span style="color: blue">new</span> System.Web.HttpCookie(<span style="color: red">'test'</span>);
<span style="color: blue">date</span>                    dateVar = today() + 1;
cookie.set_Expires(dateVar);</pre></div>Всё хорошо, пока дело не доходит до выполнения в CIL - а тут нас поджидает System.NotSupportedException с сообщением вида<div class="q">
	<div class="smallfont q_title">Цитата:</div>
	<div class="alt2 q_body">
		
			DateTimeConverter cannot convert from Microsoft.Dynamics.Ax.Xpp.AxShared.Date
		
	</div>
</div> либо<div class="q">
	<div class="smallfont q_title">Цитата:</div>
	<div class="alt2 q_body">
		
			DateTimeConverter cannot convert from Microsoft.Dynamics.Ax.Xpp.AxShared.utcdatetime
		
	</div>
</div>если rvalue - типа utcDateTime. В чем же дело? А в том, что магия неявного преобразования типов работает только в интерпретаторе байт-кода X++, а в CIL все происходит иначе, поэтому если есть хотя бы нановероятность того, что подобный ваш код будет выполняться в CIL, <i>всегда </i>выполняйте <b>явное преобразование типов</b> вида:<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code">cookie.set_Expires(CLRInterop::getObjectForAnyType(dateVar));</pre></div></div>

]]></content:encoded>
			<dc:creator>gl00mie</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=8169</guid>
		</item>
		<item>
			<title>Трудности перехода: опыт переноса модификаций с AX 3.0 SP5 EE на AX 2009 SP1 RU5 EE</title>
			<link>//axforum.info/forums/blog.php?b=246</link>
			<pubDate>Mon, 18 Jul 2011 23:10:47 GMT</pubDate>
			<description><![CDATA[Это краткие заметки ("зарубки" :)), которые я делал по ходу обновления приложения. Сразу хочу предупредить, что все, описанное ниже, относится к способу подъема модификаций из старого приложения на новое слоем и последующего их "доведения до ума". При подъеме модификаций проектами ситуация может существенно отличаться. Однако, мне известен как минимум один косяк экспорта в 3.0 (http://axforum.info/forums/showthread.php?t=20309), из-за чего перенос модификаций проектами может исказить результаты, плюс к этому стандартные инструменты обновления приложения также не рассчитаны на такой сценарий.

*Подготовка к переходу*

*Начинайте подготовку к переходу как можно раньше, возможно, за несколько месяцев до активной фазы с обновлением приложения и конвертацией данных.*
Если объем ваших модификаций невелик, можно особо не беспокоиться, однако если их много, то стоит по возможности переработать код приложения еще на версии 3.0. Дело в том, что по сравнению с 3-ей версией код приложения в AX 2009 *очень сильно* изменился. Зачастую модификации в тех или иных стандартных методах приходится поднимать на 2009-ю так: копировать куда-нибудь код измененного в 3-ке метода, удалять его с usr-слоя, возвращаясь к новому стандартному коду, а затем заново вручную накатывать сделанные ранее модификации - добавлять строки своего кода, новые параметры методов и т.п. Поэтому какие-то слишком большие куски своего кода имеет смысл выносить в отдельные методы и дергать их в модифицируемом стандартном коде, а не дописывать своей логики на 2-3 экрана (впрочем, так имеет смысл делать всегда, безотносительно перспектив перехода). Также целесообразно оценить примерное время, которое понадобится на обновление приложения, с помощью штатных средств: построить проект с выявленными конфликтами при обновлении приложения и запустить штатный отчет, который на основании этого проекта оценивает необходимое для их устранения время. Не стоит ориентироваться на опыт перехода, к примеру, на другой Service Pack для 3-ей версии, поскольку, опять же, стандартные приложения для версий 3.0 и 2009 *очень сильно* отличаются. Если планируется сразу по ходу обновления приложения начать использовать новые стандартные возможности в дополнение или вместо своих модификаций, на это следует заложить дополнительное время.

*Соберите статистику по тем объектам приложения, которые реально используются (формы, отчеты, а также наследники RunBase)*
В 2009-й есть собственная штатная инфраструктура по сбору статистики использования объектов приложения, но она работает лишь для форм и отчетов и не дает представления о том, насколько часто используются те или иные объекты приложения (см. также эту тему (http://axforum.info/forums/showthread.php?p=230824#post230824). Возможно, у вас есть какие-то уже не используемые классы (в данном случае интересны лишь наследники RunBase - для них проще всего реализовать сбор статистики), формы, отчеты - не стоит тратить время и силы на перенос их кода, если они реально не нужны. В любом случае, старое приложение, думаю, долгое время останется в пределах досягаемости, так что какие-то доработки можно будет перенести из него отдельно. Статистику по использованию объектов приложения следует собирать в течение как минимум 3-х месяцев. Для наследников RunBase код сбора статистики имеет смысл вызывать в new(), для форм - в classFactory; чтобы избежать возможных блокировок на обновлении статистики, можно воспользоваться подходом, использовавшимся в LedgerBalances*Trans: на каждый объект приложения сохранять не одну, а несколько записей, выбирая их случайным образом.

*Подготовка к обновлению приложения*

*Изучите документацию по обновлению приложения*
В руководстве по обновлению есть скупая, но важная информация по обновлению приложения. Например, кроме того, что в каталог OLD нужно скопировать все старое приложение, а к стандратному приложению - скопировать дополнительно все файлы приложения и меток для слоев выше dis, нужно в OLD-каталоге не забыть переименовать файлы dis-слоя в hfx, иначе 2009-я просто не увидит целый слой в старом приложении.

*Соберите максимально полный и актуальный набор слоев для стандартного приложения, на которое будете переходить*
В моем случае на момент начала перехода последним доступным обновлением был Rollup 4 (RU4) - RU5 появился, когда обонвление приложения было уже почти завершено, но было решено перейти на него, тем более что приложение еще не было протестировано. Кроме того, теперь отдельным слоем (sl2) идут локализованные модули "СНГ Кадровый учет" и "СНГ Зарплата", которые раньше были частью dis-слоя. Даже если у вас нет лицензий на эти модули, имеет смысл включить в состав стандартного приложения и этот слой, поскольку часть его функционала - то, что относится к конфигурационному ключу RPayHRMCommon - не закрыта лицензионными ключами, и весьма может быть, что вы так или иначе используете этот функционал. Только надо учесть, что слой sl2, поднятый на соответствующий Rollup, выходит позже самого Rollup'а; очень важно, чтобы он соответствовал Rollup'у, в противном случае *слой sl2 может затереть кое-какие модификации, сделанные на слоях syp/glp*: могут пропасть новые значения enum'ов, некоторые определения макросов в ClassDeclaration классов и т.п. Следствием всего этого может стать то, что код стандартного приложения будет компилироваться с ошибками либо просто не работать, как предполагается.

*После формирования стандартного приложения соберите перекрестные ссылки*
Вам, скорее всего, понадобится иметь два набора перекрестных ссылок: по стандартному функционалу приложения AX 2009 и по приложению версии 3.0, которое вы собираетесь обновлять. Получить полноценные перекрестные ссылки после того, как вы подложите свои слои (usr, возможно, cus и прочие выше dis-слоя) к стандартному приложению AX 2009, будет проблематично, поскольку приложение в ряде мест перестанет компилироваться. В связи с этим собрать перекрестные ссылки следует по приложению AX 2009 без слоев с кастомизациями.

*Изучите новые и допилите старые инструменты для обновления приложения*
В AX 2009 появился ряд новых замечательных инструментов для обновления, один из которых позволяет выявить конфликты при обновлении приложения. Суть его в том, что семейство классов анализирует *три* версии объекта приложения: старую (стандартное приложение 3.0 в данном случае), вашу (модифицированные объекты стандартного приложения) и "их" версию (стандартное приложение AX 2009 в данном случае) и на основе этого сравнения выявляет ряд конфликтов, а также, что очень ценно, может автоматически разрешать некоторые типы конфликтов, связанные с изменением свойств объектов. В AX 2009 по сравнению с 3.0 у таких объектов приложения, как таблицы, поля таблиц, дизайн и отдельные элементы управления форм и отчетов, появилось несколько новых свойств. Соответственно, у объектов стандартного приложения они могут быть заполнены, в то время как у объектов, поднятных из 3.0 на usr-слое, эти свойства будут иметь значения по умолчанию. Автоматическое "дозаполнение" таких свойств в соответствии с тем, каковы их значения в стандратном приложении, избавит вас от значительного объема рутинной работы.
Стоит опробовать в деле новый инструмент сравнения объектов: он был переработан, в частности, выбор слоёв по умолчанию в качестве исходного и конечного может оказаться непривычным, хотя в этом есть своя логика. Стоит "пообвыкнуться" с новым инструментом и, возможно, заранее модифицировать его работу под себя: возможно, включить сохранение размеров окна, приделать автозапуск сравнения при некоторых условиях и т.п. Также неоценимую услугу окажут перекрестные ссылки, собранные для старого приложения. Я немного модифицировал собираемые данные, чтобы можно было видеть не только где используется тот или иной объект, но и на каком слое находится код или объект приложения, его использующий. В этом случае можно легко отфильтровать данные, чтобы видеть только кастомизированный код либо, наоборот, только стандартный.

код по модификации перекрестных ссылок для отображения прикладного слоя (http://axforum.info/forums/showthread.php?t=35032)

Приведенный код может показаться излишне усложненным, но это было вызвано "особенностями" работы ядра 3.0 с объектами TreeNode: как оказалось, если создавать достаточно много таких объектов (а в таблице xRefPaths, куда пишется код слоя, может создаваться порядка 700 тысяч записей), Аксапта может "свалиться" и не довести сбор перекрестных ссылок до конца.
Также стоит учесть, что штатный механизм построения проекта выявления конфликтов при обновлении кода не рассчитан на масштабно модифицированные приложения - по всему AOT'у проект может так и не собраться (опять-таки, клиент просто свалится, вероятно, тоже из-за интенсивного использования TreeNode). Для решения этой проблемы код соответствующего класса был немного модифицирован с тем, чтобы научиться собирать такой проект по указанной подветке AOT.

код по модификации сбора проекта выявления конфликтов при обновлении кода (http://axforum.info/forums/showthread.php?p=237568#post237568)

Кроме этого, весьма вероятно, придется возиться с SqlDictionary - может понадобиться инструмент для заполнения этой таблицы по данным приложения, если в ходе обновления они как-то особо сильно разъедутся, и ядро откажется проводить синхронизацию базы (встречались случаи, когда не помогала ни синхронизация таблицы, ни проверка/синхронизация из формы администрирования SQL). К примеру, если у вас пересеклись идентификаторы полей на sys и usr-слоях, то никакие штатные синхронизации не помогут. В таких случаях приходилось, порой, просто удалять записи в SqlDictionary для проблемных полей и пересоздавать их заново.

код дозаполнения таблицы SqlDictionary для выбранной таблицы (http://axforum.info/forums/showthread.php?t=39005)

*Настройте резервное копирование*
Возможно, этот момент покажется многим очевидным, и тем не менее: обновление приложения может занять не один человеко-месяц работы, и было бы очень обидно потерять наработанные результаты, поэтому имеет смысл настроить резервное копирование пусть не всего приложения, а хотя бы обновляемого вами слоя. Также стоит создавать резервные копии базы, используемой при обновлении приложения, ведь в ней содержится ценная информация по ходу обновления (данные для проектов выявления конфликтов при обновлении кода и проч.)

*Спланируйте последовательность обновления*
Последовательность обновления отдельных типов и групп объектов приложения может оказать очень существенное влияние на сроки выполнения проекта и возможности по распараллеливанию работ. Если проект выполняется силами более чем одного человека, успешное распараллеливание работ (например, по обновлению объектов приложения и их тестированию) является одним из важнейших условий для соблюдении приемлемых сроков обновления. В целом, особенно если модификаций много, очень может пригодиться инкрементный подход к обновлению приложения: выделив наиболее важные ежедневные операции, можно начать обновлять последовательно те формы, отчеты, классы и т.д., которые в них используются. Это позволит за относительно короткие итерации получать блоки работающего обновленного функционала, который можно передать на тестирование и за счет этого распараллелить процесс обновления и тестирования приложения. При этом можно будет сконцентрироваться на наиболее важном для компании функционале, обновляя его в первую очередь и оставив обновление менее приоритетного функционала на потом. Напротив, обновление "методом большого взрыва", как и в разработке, скорее всего приведет к срыву всех мыслимых сроков проекта. В первую очередь следует обновить словарь данных (Data Dictionary) - сразу после этого можно будет начать тестовую конвертации базы, которая может занять ощутимое время. Сконвертированная (пусть пока и с ошибками) база вскоре понадобится для тестирования обновленного функционала, поэтому ее нужно получить как можно раньше.

*Собственно обновление приложения*

*Обновите словарь данных в первую очередь*
Хоть это и очевидно, но не помешает повторить. Стоит придерживаться определенной последовательности при обновлении приложения: сперва... впрочем, даже не Data Dictionary, а наиболее важные при обновлении системные классы (Application, Info, ClassFactory, Global, SysSetupFormRun, SysQuery) и формы, затем Data Dictionary, а затем уже остальные формы, отчеты, классы и проч. Если не очень повезет, то первые несколько часов обновления могут уйти на то, чтобы клиент просто запустился без ошибок.

Обратите внимание на измененные таблицы, расширенные типы - возможно, в стандартном приложении у них поменялся конфигурационный ключ, а вы со своими модификациями подняли на новое приложение определения этих объектов с конфигурационными ключами из предыдущей версии приложения. Это чревато очень серьезными неприятностями! К примеру, тот ключ, который вы "подняли" вместе с модификацией свойств таблицы, может в новом приложении стать дочерним к ключу SysDeletedObjects41 (яркий пример - ключ CRSECIS). В этом случае вы можете успешно обновить приложение, сконвертировать базу, но когда вы отключите ключи SysDeletedObjects40 и SysDeletedObjects41, часть ваших данных может пропасть! При этом штатный инструмент разрешения конфликтов свойств в таких ситуациях НЕ изменяет конфигурационный ключ, считая это свойство объектов доступным лишь для чтения. Чтобы продиагностировать и при необходимости исправить ситуацию, можно воспользоваться кодом по приведенной ниже ссылке. Следует также внимательно просмотреть, какие конфигурационные ключи сделаны дочерними к SysDeletedObjects41 - возможно, вы используете какой-то функционал, который привязан к этим ключам. Стоит внимательно проверить по перекрестным ссылкам, какие таблицы, поля или расширенные типы привязаны к этим ключам, используются ли они в ваших модификациях.

код диагностики и исправления "съехавших" конфигурационных ключей на таблицах (http://axforum.info/forums/showthread.php?p=240192#post240192)

*Посмотрите, не изменились ли идентификаторы модифицированных вами стандартных классов*
Если вам "повезло", и идентификатор модифицированного вами класса стандартного приложения изменился в новой версии, то ядро может "забыть" про ваши модификации, хотя они будут все также находиться на соответствующем слое (предположительно usr), класс же будет в AOT выглядеть так, будто он не модифицирован вовсе. На сравнении таких классов будут выдаваться предупреждения, что идентификаторы отличаются на разных слоях. К примеру, при прогоне тестового джоба на приложениях AX 3.0 SP5 FP1 EE (в OLD) и AX 2009 SP1 RU4 EE + слой sl2 с модулями "СНГ Зарплата" и "СНГ Кадровый учет" было найдено 66 таких классов, из них лишь 4 на sys-слое, остальные - на gls/sl2.
// выявляет отличия в идентификаторах классов стандартного приложения, сравнивая текущее приложение и приложение в OLD-каталоге
UtilIdElements      ue;
UtilIdElementsOld   ueOld;
identifiername      name;
SetEnumerator       setEnum;
MapEnumerator       mapEnum;
Map                 mapOldName2Id   = new Map( typeof(ue.name), typeof(ue.Id) );
Map                 mapNewName2Id   = new Map( typeof(ue.name), typeof(ue.Id) );
Set                 setOfCommonNames;
classid             newId;
classid             oldId;
Counter             n;
;
setprefix( @"Классы стандартного приложения, у которых изменился идентификатор" );
while select name, id, utilLevel
    from    ue
    where   ue.recordType       == UtilElementType::Class
        &&  ue.utilLevel        <  UtilEntryLevel::bus
{
    mapNewName2Id.insert( ue.name, ue.Id );
}
while select name, id, utilLevel
    from    ueOld
    where   ueOld.recordType    == UtilElementType::Class
        &&  ueOld.utilLevel     <  UtilEntryLevel::bus
{
    mapOldName2Id.insert( ueOld.name, ueOld.Id );
}
setOfCommonNames = Set::intersection( mapNewName2Id.keySet(), mapOldName2Id.keySet() );
setEnum = setOfCommonNames.getEnumerator();
while (setEnum.moveNext())
{
    name    = setEnum.current();
    oldId   = mapOldName2Id.lookup( name );
    newId   = mapNewName2Id.lookup( name );
    if (newId != oldId)
    {
        select firstonly name, id, utilLevel
            from    ue
            order by utilLevel desc
            where   ue.name             == name
                &&  ue.recordType       == UtilElementType::Class
                &&  ue.utilLevel        <  UtilEntryLevel::bus
                    ;
        select firstonly name, id, utilLevel
            from    ueOld
            order by utilLevel desc
            where   ueOld.name          == name
                &&  ueOld.recordType    == UtilElementType::Class
                &&  ueOld.utilLevel     <  UtilEntryLevel::bus
                    ;
        info( strfmt( @"%1 old %2 id %3, new %4 id %5", name, ueOld.utilLevel, ueOld.Id, ue.utilLevel, ue.Id ));
        n++;
    }
}
info( strfmt( @"Найдено %1 объектов", n ) );

*Посмотрите, не пересеклись ли идентификаторы табличных полей в стандартном приложении и созданные вами*
Это касается как собственно полей таблиц (например, в CustParameters у 3-х полей с sys-слоя идентификаторы - из диапазона, отведенного usr-слою), так и полей Map. С Map'ами нужно быть очень осторожным, потому что их "кривизна" может приводить к очень труднодиагностируемым ошибкам: отладчик по-прежнему "не понимает" Map'ы и соотв. переменные показывает как табличные переменные той или иной реальной таблицы, позволяя посмотреть те же поля лишь по названиям в таблице, а не в Map'е.

код поиска объектов стандартного приложения с идентификаторами из диапазона usr-слоя (http://axforum.info/forums/showthread.php?p=228932#post228932)

*Обратите внимание на стандартные методы, у которых изменилось число параметров*
Очень внимательно обновляйте те методы в модифицированном вами коде, где в стандартном приложении добавились новые параметры со значениями по умолчанию. Если их типы совпадают с типами добавленных вами параметров (обычно boolean), то некорректно обновленный код может начать вызывать эти методы, передавая значения не тех параметров, которые изначально задумывались автором модификации. Это касается, к примеру, таких методов, как SalesLineType.insert(), SalesLineType.update() и т.п.

После обновления приложения стоит заняться вопросами конвертации базы, но это - отдельная история...]]></description>
			<content:encoded><![CDATA[<div>Это краткие заметки (&quot;зарубки&quot; :)), которые я делал по ходу обновления приложения. Сразу хочу предупредить, что все, описанное ниже, относится к способу подъема модификаций из старого приложения на новое слоем и последующего их &quot;доведения до ума&quot;. При подъеме модификаций проектами ситуация может существенно отличаться. Однако, мне известен как минимум <a href="http://axforum.info/forums/showthread.php?t=20309" target="_blank">один косяк экспорта в 3.0</a>, из-за чего перенос модификаций проектами может исказить результаты, плюс к этому стандартные инструменты обновления приложения также не рассчитаны на такой сценарий.<br />
<br />
<font size="4"><b>Подготовка к переходу</b></font><br />
<br />
<b>Начинайте подготовку к переходу как можно раньше, возможно, за несколько месяцев до активной фазы с обновлением приложения и конвертацией данных.</b><br />
Если объем ваших модификаций невелик, можно особо не беспокоиться, однако если их много, то стоит по возможности переработать код приложения еще на версии 3.0. Дело в том, что по сравнению с 3-ей версией код приложения в AX 2009 <i><b>очень сильно</b></i> изменился. Зачастую модификации в тех или иных стандартных методах приходится поднимать на 2009-ю так: копировать куда-нибудь код измененного в 3-ке метода, удалять его с usr-слоя, возвращаясь к новому стандартному коду, а затем заново вручную накатывать сделанные ранее модификации - добавлять строки своего кода, новые параметры методов и т.п. Поэтому какие-то слишком большие куски своего кода имеет смысл выносить в отдельные методы и дергать их в модифицируемом стандартном коде, а не дописывать своей логики на 2-3 экрана (впрочем, так имеет смысл делать всегда, безотносительно перспектив перехода). Также целесообразно оценить примерное время, которое понадобится на обновление приложения, с помощью штатных средств: построить проект с выявленными конфликтами при обновлении приложения и запустить штатный отчет, который на основании этого проекта оценивает необходимое для их устранения время. Не стоит ориентироваться на опыт перехода, к примеру, на другой Service Pack для 3-ей версии, поскольку, опять же, стандартные приложения для версий 3.0 и 2009 <i><b>очень сильно</b></i> отличаются. Если планируется сразу по ходу обновления приложения начать использовать новые стандартные возможности в дополнение или вместо своих модификаций, на это следует заложить дополнительное время.<br />
<br />
<b>Соберите статистику по тем объектам приложения, которые реально используются (формы, отчеты, а также наследники RunBase)</b><br />
В 2009-й есть собственная штатная инфраструктура по сбору статистики использования объектов приложения, но она работает лишь для форм и отчетов и не дает представления о том, насколько часто используются те или иные объекты приложения (см. также <a href="http://axforum.info/forums/showthread.php?p=230824#post230824" target="_blank">эту тему</a>. Возможно, у вас есть какие-то уже не используемые классы (в данном случае интересны лишь наследники RunBase - для них проще всего реализовать сбор статистики), формы, отчеты - не стоит тратить время и силы на перенос их кода, если они реально не нужны. В любом случае, старое приложение, думаю, долгое время останется в пределах досягаемости, так что какие-то доработки можно будет перенести из него отдельно. Статистику по использованию объектов приложения следует собирать в течение как минимум 3-х месяцев. Для наследников RunBase код сбора статистики имеет смысл вызывать в new(), для форм - в classFactory; чтобы избежать возможных блокировок на обновлении статистики, можно воспользоваться подходом, использовавшимся в LedgerBalances*Trans: на каждый объект приложения сохранять не одну, а несколько записей, выбирая их случайным образом.<br />
<br />
<font size="4"><b>Подготовка к обновлению приложения</b></font><br />
<br />
<b>Изучите документацию по обновлению приложения</b><br />
В руководстве по обновлению есть скупая, но важная информация по обновлению приложения. Например, кроме того, что в каталог OLD нужно скопировать все старое приложение, а к стандратному приложению - скопировать дополнительно все файлы приложения и меток для слоев выше dis, нужно в OLD-каталоге не забыть переименовать файлы dis-слоя в hfx, иначе 2009-я просто не увидит целый слой в старом приложении.<br />
<br />
<b>Соберите максимально полный и актуальный набор слоев для стандартного приложения, на которое будете переходить</b><br />
В моем случае на момент начала перехода последним доступным обновлением был Rollup 4 (RU4) - RU5 появился, когда обонвление приложения было уже почти завершено, но было решено перейти на него, тем более что приложение еще не было протестировано. Кроме того, теперь отдельным слоем (sl2) идут локализованные модули &quot;СНГ Кадровый учет&quot; и &quot;СНГ Зарплата&quot;, которые раньше были частью dis-слоя. Даже если у вас нет лицензий на эти модули, имеет смысл включить в состав стандартного приложения и этот слой, поскольку часть его функционала - то, что относится к конфигурационному ключу RPayHRMCommon - не закрыта лицензионными ключами, и весьма может быть, что вы так или иначе используете этот функционал. Только надо учесть, что слой sl2, поднятый на соответствующий Rollup, выходит позже самого Rollup'а; очень важно, чтобы он соответствовал Rollup'у, в противном случае <i><b>слой sl2 может затереть кое-какие модификации, сделанные на слоях syp/glp</b></i>: могут пропасть новые значения enum'ов, некоторые определения макросов в ClassDeclaration классов и т.п. Следствием всего этого может стать то, что код стандартного приложения будет компилироваться с ошибками либо просто не работать, как предполагается.<br />
<br />
<b>После формирования стандартного приложения соберите перекрестные ссылки</b><br />
Вам, скорее всего, понадобится иметь два набора перекрестных ссылок: по стандартному функционалу приложения AX 2009 и по приложению версии 3.0, которое вы собираетесь обновлять. Получить полноценные перекрестные ссылки после того, как вы подложите свои слои (usr, возможно, cus и прочие выше dis-слоя) к стандартному приложению AX 2009, будет проблематично, поскольку приложение в ряде мест перестанет компилироваться. В связи с этим собрать перекрестные ссылки следует по приложению AX 2009 без слоев с кастомизациями.<br />
<br />
<b>Изучите новые и допилите старые инструменты для обновления приложения</b><br />
В AX 2009 появился ряд новых замечательных инструментов для обновления, один из которых позволяет выявить конфликты при обновлении приложения. Суть его в том, что семейство классов анализирует <b>три</b> версии объекта приложения: старую (стандартное приложение 3.0 в данном случае), вашу (модифицированные объекты стандартного приложения) и &quot;их&quot; версию (стандартное приложение AX 2009 в данном случае) и на основе этого сравнения выявляет ряд конфликтов, а также, что очень ценно, может автоматически разрешать некоторые типы конфликтов, связанные с изменением свойств объектов. В AX 2009 по сравнению с 3.0 у таких объектов приложения, как таблицы, поля таблиц, дизайн и отдельные элементы управления форм и отчетов, появилось несколько новых свойств. Соответственно, у объектов стандартного приложения они могут быть заполнены, в то время как у объектов, поднятных из 3.0 на usr-слое, эти свойства будут иметь значения по умолчанию. Автоматическое &quot;дозаполнение&quot; таких свойств в соответствии с тем, каковы их значения в стандратном приложении, избавит вас от значительного объема рутинной работы.<br />
Стоит опробовать в деле новый инструмент сравнения объектов: он был переработан, в частности, выбор слоёв по умолчанию в качестве исходного и конечного может оказаться непривычным, хотя в этом есть своя логика. Стоит &quot;пообвыкнуться&quot; с новым инструментом и, возможно, заранее модифицировать его работу под себя: возможно, включить сохранение размеров окна, приделать автозапуск сравнения при некоторых условиях и т.п. Также неоценимую услугу окажут перекрестные ссылки, собранные для старого приложения. Я немного модифицировал собираемые данные, чтобы можно было видеть не только где используется тот или иной объект, но и на каком слое находится код или объект приложения, его использующий. В этом случае можно легко отфильтровать данные, чтобы видеть только кастомизированный код либо, наоборот, только стандартный.<br />
<br />
<a href="http://axforum.info/forums/showthread.php?t=35032" target="_blank">код по модификации перекрестных ссылок для отображения прикладного слоя</a><br />
<br />
Приведенный код может показаться излишне усложненным, но это было вызвано &quot;особенностями&quot; работы ядра 3.0 с объектами TreeNode: как оказалось, если создавать достаточно много таких объектов (а в таблице xRefPaths, куда пишется код слоя, может создаваться порядка 700 тысяч записей), Аксапта может &quot;свалиться&quot; и не довести сбор перекрестных ссылок до конца.<br />
Также стоит учесть, что штатный механизм построения проекта выявления конфликтов при обновлении кода не рассчитан на масштабно модифицированные приложения - по всему AOT'у проект может так и не собраться (опять-таки, клиент просто свалится, вероятно, тоже из-за интенсивного использования TreeNode). Для решения этой проблемы код соответствующего класса был немного модифицирован с тем, чтобы научиться собирать такой проект по указанной подветке AOT.<br />
<br />
<a href="http://axforum.info/forums/showthread.php?p=237568#post237568" target="_blank">код по модификации сбора проекта выявления конфликтов при обновлении кода</a><br />
<br />
Кроме этого, весьма вероятно, придется возиться с SqlDictionary - может понадобиться инструмент для заполнения этой таблицы по данным приложения, если в ходе обновления они как-то особо сильно разъедутся, и ядро откажется проводить синхронизацию базы (встречались случаи, когда не помогала ни синхронизация таблицы, ни проверка/синхронизация из формы администрирования SQL). К примеру, если у вас пересеклись идентификаторы полей на sys и usr-слоях, то никакие штатные синхронизации не помогут. В таких случаях приходилось, порой, просто удалять записи в SqlDictionary для проблемных полей и пересоздавать их заново.<br />
<br />
<a href="http://axforum.info/forums/showthread.php?t=39005" target="_blank">код дозаполнения таблицы SqlDictionary для выбранной таблицы</a><br />
<br />
<b>Настройте резервное копирование</b><br />
Возможно, этот момент покажется многим очевидным, и тем не менее: обновление приложения может занять не один человеко-месяц работы, и было бы очень обидно потерять наработанные результаты, поэтому имеет смысл настроить резервное копирование пусть не всего приложения, а хотя бы обновляемого вами слоя. Также стоит создавать резервные копии базы, используемой при обновлении приложения, ведь в ней содержится ценная информация по ходу обновления (данные для проектов выявления конфликтов при обновлении кода и проч.)<br />
<br />
<b>Спланируйте последовательность обновления</b><br />
Последовательность обновления отдельных типов и групп объектов приложения может оказать очень существенное влияние на сроки выполнения проекта и возможности по распараллеливанию работ. Если проект выполняется силами более чем одного человека, успешное распараллеливание работ (например, по обновлению объектов приложения и их тестированию) является одним из важнейших условий для соблюдении приемлемых сроков обновления. В целом, особенно если модификаций много, очень может пригодиться инкрементный подход к обновлению приложения: выделив наиболее важные ежедневные операции, можно начать обновлять последовательно те формы, отчеты, классы и т.д., которые в них используются. Это позволит за относительно короткие итерации получать блоки работающего обновленного функционала, который можно передать на тестирование и за счет этого распараллелить процесс обновления и тестирования приложения. При этом можно будет сконцентрироваться на наиболее важном для компании функционале, обновляя его в первую очередь и оставив обновление менее приоритетного функционала на потом. Напротив, обновление &quot;методом большого взрыва&quot;, как и в разработке, скорее всего приведет к срыву всех мыслимых сроков проекта. В первую очередь следует обновить словарь данных (Data Dictionary) - сразу после этого можно будет начать тестовую конвертации базы, которая может занять ощутимое время. Сконвертированная (пусть пока и с ошибками) база вскоре понадобится для тестирования обновленного функционала, поэтому ее нужно получить как можно раньше.<br />
<br />
<font size="4"><b>Собственно обновление приложения</b></font><br />
<br />
<b>Обновите словарь данных в первую очередь</b><br />
Хоть это и очевидно, но не помешает повторить. Стоит придерживаться определенной последовательности при обновлении приложения: сперва... впрочем, даже не Data Dictionary, а наиболее важные при обновлении системные классы (Application, Info, ClassFactory, Global, SysSetupFormRun, SysQuery) и формы, затем Data Dictionary, а затем уже остальные формы, отчеты, классы и проч. Если не очень повезет, то первые несколько часов обновления могут уйти на то, чтобы клиент просто запустился без ошибок.<br />
<br />
Обратите внимание на измененные таблицы, расширенные типы - возможно, в стандартном приложении у них поменялся конфигурационный ключ, а вы со своими модификациями подняли на новое приложение определения этих объектов с конфигурационными ключами из предыдущей версии приложения. Это чревато очень серьезными неприятностями! К примеру, тот ключ, который вы &quot;подняли&quot; вместе с модификацией свойств таблицы, может в новом приложении стать дочерним к ключу SysDeletedObjects41 (яркий пример - ключ CRSECIS). В этом случае вы можете успешно обновить приложение, сконвертировать базу, но когда вы отключите ключи SysDeletedObjects40 и SysDeletedObjects41, часть ваших данных может пропасть! При этом штатный инструмент разрешения конфликтов свойств в таких ситуациях НЕ изменяет конфигурационный ключ, считая это свойство объектов доступным лишь для чтения. Чтобы продиагностировать и при необходимости исправить ситуацию, можно воспользоваться кодом по приведенной ниже ссылке. Следует также внимательно просмотреть, какие конфигурационные ключи сделаны дочерними к SysDeletedObjects41 - возможно, вы используете какой-то функционал, который привязан к этим ключам. Стоит внимательно проверить по перекрестным ссылкам, какие таблицы, поля или расширенные типы привязаны к этим ключам, используются ли они в ваших модификациях.<br />
<br />
<a href="http://axforum.info/forums/showthread.php?p=240192#post240192" target="_blank">код диагностики и исправления &quot;съехавших&quot; конфигурационных ключей на таблицах</a><br />
<br />
<b>Посмотрите, не изменились ли идентификаторы модифицированных вами стандартных классов</b><br />
Если вам &quot;повезло&quot;, и идентификатор модифицированного вами класса стандартного приложения изменился в новой версии, то ядро может &quot;забыть&quot; про ваши модификации, хотя они будут все также находиться на соответствующем слое (предположительно usr), класс же будет в AOT выглядеть так, будто он не модифицирован вовсе. На сравнении таких классов будут выдаваться предупреждения, что идентификаторы отличаются на разных слоях. К примеру, при прогоне тестового джоба на приложениях AX 3.0 SP5 FP1 EE (в OLD) и AX 2009 SP1 RU4 EE + слой sl2 с модулями &quot;СНГ Зарплата&quot; и &quot;СНГ Кадровый учет&quot; было найдено 66 таких классов, из них лишь 4 на sys-слое, остальные - на gls/sl2.<br />
<div class="xpp"><div class="smallfont xpp_title">X++:</div><pre class="alt2 xpp_code"><span style="color: green">// выявляет отличия в идентификаторах классов стандартного приложения, сравнивая текущее приложение и приложение в OLD-каталоге
</span>UtilIdElements      ue;
UtilIdElementsOld   ueOld;
identifiername      name;
SetEnumerator       setEnum;
MapEnumerator       mapEnum;
Map                 mapOldName2Id   = <span style="color: blue">new</span> Map( typeof(ue.name), typeof(ue.Id) );
Map                 mapNewName2Id   = <span style="color: blue">new</span> Map( typeof(ue.name), typeof(ue.Id) );
Set                 setOfCommonNames;
classid             newId;
classid             oldId;
Counter             n;
;
setprefix( @<span style="color: red">&quot;Классы стандартного приложения, у которых изменился идентификатор&quot;</span> );
<span style="color: blue">while</span> <span style="color: blue">select</span> name, id, utilLevel
    <span style="color: blue">from</span>    ue
    <span style="color: blue">where</span>   ue.recordType       == UtilElementType::<span style="color: blue">Class</span>
        &amp;&amp;  ue.utilLevel        &lt;  UtilEntryLevel::bus
{
    mapNewName2Id.insert( ue.name, ue.Id );
}
<span style="color: blue">while</span> <span style="color: blue">select</span> name, id, utilLevel
    <span style="color: blue">from</span>    ueOld
    <span style="color: blue">where</span>   ueOld.recordType    == UtilElementType::<span style="color: blue">Class</span>
        &amp;&amp;  ueOld.utilLevel     &lt;  UtilEntryLevel::bus
{
    mapOldName2Id.insert( ueOld.name, ueOld.Id );
}
setOfCommonNames = Set::intersection( mapNewName2Id.keySet(), mapOldName2Id.keySet() );
setEnum = setOfCommonNames.getEnumerator();
<span style="color: blue">while</span> (setEnum.moveNext())
{
    name    = setEnum.current();
    oldId   = mapOldName2Id.lookup( name );
    newId   = mapNewName2Id.lookup( name );
    <span style="color: blue">if</span> (newId != oldId)
    {
        <span style="color: blue">select</span> <span style="color: blue">firstonly</span> name, id, utilLevel
            <span style="color: blue">from</span>    ue
            <span style="color: blue">order</span> <span style="color: blue">by</span> utilLevel <span style="color: blue">desc</span>
            <span style="color: blue">where</span>   ue.name             == name
                &amp;&amp;  ue.recordType       == UtilElementType::<span style="color: blue">Class</span>
                &amp;&amp;  ue.utilLevel        &lt;  UtilEntryLevel::bus
                    ;
        <span style="color: blue">select</span> <span style="color: blue">firstonly</span> name, id, utilLevel
            <span style="color: blue">from</span>    ueOld
            <span style="color: blue">order</span> <span style="color: blue">by</span> utilLevel <span style="color: blue">desc</span>
            <span style="color: blue">where</span>   ueOld.name          == name
                &amp;&amp;  ueOld.recordType    == UtilElementType::<span style="color: blue">Class</span>
                &amp;&amp;  ueOld.utilLevel     &lt;  UtilEntryLevel::bus
                    ;
        info( strfmt( @<span style="color: red">&quot;%1 old %2 id %3, new %4 id %5&quot;</span>, name, ueOld.utilLevel, ueOld.Id, ue.utilLevel, ue.Id ));
        n++;
    }
}
info( strfmt( @<span style="color: red">&quot;Найдено %1 объектов&quot;</span>, n ) );</pre></div><br />
<b>Посмотрите, не пересеклись ли идентификаторы табличных полей в стандартном приложении и созданные вами</b><br />
Это касается как собственно полей таблиц (например, в CustParameters у 3-х полей с sys-слоя идентификаторы - из диапазона, отведенного usr-слою), так и полей Map. С Map'ами нужно быть очень осторожным, потому что их &quot;кривизна&quot; может приводить к очень труднодиагностируемым ошибкам: отладчик по-прежнему &quot;не понимает&quot; Map'ы и соотв. переменные показывает как табличные переменные той или иной реальной таблицы, позволяя посмотреть те же поля лишь по названиям в таблице, а не в Map'е.<br />
<br />
<a href="http://axforum.info/forums/showthread.php?p=228932#post228932" target="_blank">код поиска объектов стандартного приложения с идентификаторами из диапазона usr-слоя</a><br />
<br />
<b>Обратите внимание на стандартные методы, у которых изменилось число параметров</b><br />
Очень внимательно обновляйте те методы в модифицированном вами коде, где в стандартном приложении добавились новые параметры со значениями по умолчанию. Если их типы совпадают с типами добавленных вами параметров (обычно boolean), то некорректно обновленный код может начать вызывать эти методы, передавая значения не тех параметров, которые изначально задумывались автором модификации. Это касается, к примеру, таких методов, как SalesLineType.insert(), SalesLineType.update() и т.п.<br />
<br />
После обновления приложения стоит заняться вопросами конвертации базы, но это - отдельная история...</div>

]]></content:encoded>
			<dc:creator>gl00mie</dc:creator>
			<guid isPermaLink="true">//axforum.info/forums/blog.php?b=246</guid>
		</item>
	</channel>
</rss>
