четверг, 8 марта 2018 г.

Классы коллекций в Dynamics AX


Введение

Для работы со структурами данных в Dynamics AX предусмотрены несколько классов коллекций, которые позволяют хранить и извлекать не только данные, но и записи, и объекты.
Каждый класс имеет свои ограничения на хранимые типы данных и доступ к ним, в таблице ниже они описаны:

Класс
Описание
Тип данных
Доступ
List
Значения хранятся последовательно с возможностью добавления новых значений в начало или конец списка.
Все значения должны быть одного типа
Используется ListIterator или ListEnumerator
Set
Значения хранятся неупорядоченно. Повторяющиеся значения не добавляются.
Все значения должны быть одного типа
Используется SetIterator или SetEnumerator
Map
Значения имеют уникальный ключ.
Ключи должны быть одного типа. Значения для каждого ключа также должны быть одного типа.
Используется MapIterator или MapEnumerator
Array
Значения имеют уникальный числовой ключ.
Все значения должны быть одного типа
Доступ осуществляется по числовому ключу.
Struct
Значения имеют уникальный ключ строкового типа
Ключ должен быть строковым. Значения могут быть любого типа.
Доступ осуществляется по строковому ключу.

По сравнению с контейнерами, классы коллекций имеют более функциональный интерфейс.
Все классы коллекций содержатся в памяти, поэтому, при работе с ними, необходимо учитывать их размер. Если необходимо обработать большой объем данных, то следует рассмотреть альтернативные варианты, например, временные таблицы.
Рассмотрим сами классы ниже.

List (Список)

В списке можно хранить данные одного типа в упорядоченной последовательности. Элементы могут быть любого типа X++. Тип определяется при создании и не может быть изменен после инициализации. Элементы могут добавляться в начало или конец списка. Основные методы для работы со списками описаны в таблице ниже:


addEnd(_item)
Добавляет элемент _item в конец списка
addStart(_item)
Добавляет элемент _item в начало списка
appendList(_list)
Добавляет элементы списка _list в конец текущего списка
elements
Возвращает количество элементов в списке
getEnumerator
Возвращает указатель для извлечения элементов списка

В примере ниже создаются и объединяются 2 списка:


Результат:

Список можно инициализировать и через контейнер:



Результат:


Set (Множество)

Множества используются для хранения уникальных значений, каждый элемент множества является ключом, по которому данные автоматически сортируются при добавлении нового элемента – этим множества отличаются от списков. Множества могут содержать любые типы X++.
При добавлении в множество элемента, который уже в нем есть, ничего не произойдет - не будет никаких записей о том, сколько таких вставок было. Например, если множество типа Integer (Types::Integer) содержит только значение 7, то повторный вызов метода вставки еще одного значения 7 не даст никакого эффекта. Не важно сколько таких вставок будет сделано, множество так и будет содержать одно значение – 7.
Отсюда следуют 2 полезных варианта использования множеств:
  • хранение уникальных значений (например, RecId для избегания повторных действий над одной записью)
  • сортировка потока произвольных значений


С множествами в X++ можно работать как с математическими множествами, для этого есть операции, позволяющие получать новые множества, используя 3 метода.




Наиболее полезные методы для работы с множествами описаны в таблице ниже:


add(_item)
Добавляет элемент _item в множество
delete(_item)
Удаляет выбранный элемент _item из множества
elements
Возвращает количество элементов в множестве
getEnumerator
Возвращает enumerator для выборки из множества
in(_item)
Возвращает true, если элемент _item есть в текущем множестве
difference(_set1, _set2)
Возвращает элементы, которые есть в множестве _set1, но которых нет в множестве _set2
intersection(_set1, _set2)
Возвращает новое множество, которое содержит элементы, содержащиеся в обоих множествах _set1 и _set2
union(_set1, _set2)
Возвращает новое множество, которое содержит элементы из обоих множеств _set1 и _set2.


В примере ниже демонстрируется сортировка при заполнении множества:



Результат (нет повторяющихся значений и все элементы упорядочены):



В примере ниже демонстрируется работа с множествами X++ как с математическими множествами:



Результат:




Map (Карта соответствий)

Карта хранит пару ключ-значение. Для каждого ключа есть соответствующее значение. Ключи уникальны и все одного типа. Значения тоже все одного типа, но могут быть неуникальными. В качестве ключей и значений могут использоваться любые типы, при этом типы ключей и значений в одной карте могут отличаться. По картам быстро осуществляется поиск, что делает их полезными для кэширования данных.
Несколько ключей могут относиться к одному и тому же значению, но один ключ не может относиться к нескольким значениям. При добавлении новой пары ключ-значение в карту, где уже есть такой ключ с другим значением, связь меняется так, что ключ будет указывать на новое значение (значение у старого ключа обновится).

Наиболее полезные методы для работы с множествами описаны в таблице ниже:


delete(_key)
Удаляет ключ _key (и, соответственно, связанное значение) из карты
elements
Возвращает количество пар ключ-элемент в карте
exists(_key)
Возвращает true, если ключ _key есть в карте
insert(_key, _value)
Вставляет пару ключ-значение, где ключ - _key, а значение -  _value. Другими словами, поиски по ключу _key вернут значение _value. Если ключ _key уже есть в карте, то связанное с этим ключом значение обновится, так что новым значением для этого ключа станет _value.
lookup(_key)
Возвращает значение, связанное с ключом _key.

Важно: вызов метода lookup для ключа, которого нет в карте выдаст ошибку. Таким образом, следует сначала вызывать метод exists для проверки существования ключа.
Класс MapEnumerator используется для извлечений данных из карты. Для извлечения ключа и значения из объекта MapEnumerator используются методы currentKey() и currentValue().

Ниже пример работы с картами:



Результат:



Struct (Структура)

В структурах данные группируются в единое целое. Использоваться могут все типы из Types.
Класс Struct – один из самых простых классов коллекций. Использование структур похоже на использование контейнеров, которые имеют небольшое количество записей на конкретных позициях. Основное отличие от контейнеров в том, что структуры – это класс, а контейнеры – встроенный в X++ тип данных. В структурах используются строковые индексы, в то время как в контейнерах используются номерные индексы.
Основное преимущество структур – возможность динамического добавления новых элементов, без необходимости создавать новый тип в AOT.

Пример использования класса Struct:


Результат:



Array (Массив)

Объекты Array могут содержать элементы любого типа, в отличие от встроенного типа данных «массив» в X++. Значения в Array хранятся последовательно. Array можно расширить по необходимости, поэтому не нужно определять его размер при создании объекта. Также как и у массивов в X++ нумерация в Array начинается с единицы, а не нуля.

Пример использования Array:



Результат:






Помимо описанных классов есть еще пара не самых популярных: Stack и StackBase.

Stack (Стек)

В стеке способ организации данных построен по принципу LIFO (last in first out, последним пришел - первым ушел). Элемент добавляется вызовом метода push, а удаляется методом pop. Класс Stack в Dynamics AX может хранить только экземпляры контейнеров, соответственно, данные в стеке могут быть такого же типа, как и в контейнерах.

Пример использования стека:



Результат:



StackBase (Базовый стек)

У класса StackBase, в отличие от Stack, нет ограничения на хранение только экземпляров контейнеров. Класс StackBase предоставляет ту же функциональность, как и Stack с возможностью хранить данные любого типа, включая объекты.

Пример использования StackBase:



Результат:




На этом, пожалуй, описание классов коллекций завершу.

Happy DAXing!

понедельник, 13 ноября 2017 г.

Немного о CIL

В Microsoft Dynamics AX код может быть откомпилирован в CIL и запущен в .NET CLR. 
Разберемся по порядку что это значит.

Компиляция – интерпретация исходного кода в машинноисполняемый формат, который может быть исполнен на сервере или клиенте.

CIL (Common Intermediate Language) – единый переходный язык, работающий совместно с CLI (Common Language Infrastructure) – многоязыковой инфраструктурой, базирующейся на наборе правил языков программирования, которые компилируются в CIL. Одним из самых важных свойств CIL является набор инструкций, не зависящих от платформы и ЦП, что позволяет выполнять код в разных средах.

На рисунке ниже показано, как языки первично компилируются в CIL после чего CLR (многоязыковая среда выполнения) компилирует платформонезависимый код CIL в код, читаемый машиной:





Многоязыковая среда выполнения (CLR – Common Language Runtime) понимает CIL и знает как выполнять этот код. CLR не нужно понимать все языки (C#, VB или F#), ей достаточно знать только один язык, т.к. все они преобразуются в CIL.

Microsoft Dynamics AX может преобразовывать откомпилированный X++ код в код CIL, соответственно, может запускать код X++ в CIL. Необходимо не забывать про CIL при разработке, т.к. код, запускаемый на сервере, такой как пакетные задания и службы, будет запускаться в CIL и, соответственно, такой код X++ нужно предварительно откомпилировать в CIL.

Т.к. компиляция в CIL требует времени, то она не выполняется автоматически при компиляции X++. Необходимо выполнять ее вручную при изменении кода, используя кнопки компиляции на рабочем интерфейсе разработчика.

Можно запустить полную или инкрементную CIL-компиляцию. Основная разница между ними в том, что инкрементная компиляция выполняется только для объектов, измененных со времени последней компиляции, а полная компиляция выполняется для всех объектов. Инкрементная компиляция выполняется быстрее, чем полная, но необходимо понимать, что не всегда достаточно инкрементной компиляции.

Итак, CIL-компиляция выполнена. Но что изменилось и где можно найти результаты этого процесса? Ответ на этот вопрос можно найти в папке bin на сервере. По умолчанию она находится здесь: %ProgramFiles%\Microsoft Dynamics AX\60\Server\<Server Name>\bin\XppIL. 


В этой папке можно найти итоговый файл сборки Dynamics.Ax.Application.dll вместе со списком файлов *.NetModule. Файлы *.NetModule отличаются от сборок .Net, - они не содержат манифеста сборки. Файлы *.NetModule содержат только типовые метаданные и откомпилированный код. Также в этой папке можно найти файлы с разрешением .xpp - эти файлы содержат исходный X++ код и могут использоваться при отладке кода X++ в Visual Studio, таким образом редактор и отладчик могут показывать актуальный исходный код.

Примечание: если установлено несколько экземпляров AOS, то необходимо остановить все экземпляры, и затем перезапустить их. Это необходимо для того, чтобы экземпляры AOS загрузили обновленную сборку.



ПС: если в материале есть какие-либо неточности, то прошу поправить в личном сообщении или в комментарии.
Happy DAXing!

суббота, 16 сентября 2017 г.

Синхронизация словаря данных


В работе с АХ крайне важным является выполнение синхронизации словаря данных [Data dictionary]. Если не понимать, что это за процесс и для чего он нужен, то в какой-то момент можно столкнуться с проблемами, связанными с синхронизацией.

Запускается синхронизация словаря данных из AOT:


Попробуем разобраться что же такое "синхронизация словаря данных".
Рассмотрим как АХ работает с SQL-сервером. Определения таблиц (имя таблицы, имена столбцов, их тип, длина и т.п.) хранятся в AX. Однако, так или иначе эти данные должны содержаться на SQL-сервере, иначе невозможно будет хранить данные в БД. 

Для этого и используется синхронизация словаря данных. Синхронизация происходит в случае, если таблица еще не существует на SQL сервере, если же таблица существует, то при синхронизации происходит поиск изменений в таблице и АХ пытается внести эти изменения на SQL-сервер. 

На самом деле все довольно просто. Вы просто запускаете синхронизацию таблицы, которую создали, и АХ обработает расхождения. После запуска процедуры синхронизации SQL получит все необходимые данные по физической таблице и сервер сможет работать с данными.

SQL Словарь

Теперь рассмотрим идентификаторы объектов (ID) и SQL-словарь таблиц на SQL-сервере. Каждый объект в АХ получает идентификатор. В АХ идентификатор присваивается объекту в момент его создания (также в момент импорта *.xpo или нового объекта в AOT, или импорта модели).

Разберем как АХ работает с таблицами на SQL сервере. Система берет параметры таблицы в AOTе и переносит их (поля, их названия, длину, тип и т.п.) на SQL. Как же АХ отслеживает, что хранится на SQL сервере? Будет ли достаточно проверки имен объектов? 
Таблица на SQL может быть проверена по имени в AOT. Итак, представим, что изменилось имя поля таблицы с columnA на columnB. Таблица, которая изменилась, не содержит никаких пометок, что ее поля были изменены.

Что же произойдет на SQL сервере, когда будет выполняется синхронизация словаря данных по таблице? Останется старое поле columnA и создастся новое поле columnB? Удалятся ли значения в исходном поле?
Для этого используются «системные» таблицы AX, в которых хранится информация о том, что АХ записала на SQL сервер. Таблица SQLDictionary содержит имена объектов АХ, их имена на SQL сервере и их ID в AX.

ID объектов АХ не могут быть изменены разработчиком вручную в IDE (Integrated Development Environment - интегрированной среде разработки), таким образом, это значение может использоваться для идентификации объекта в случае изменения его имени. Итак, теперь можно дать ответ на вопрос, указанный выше: если изменится имя столбца, то АХ будет искать в SQLDictionary запись, соответствующую столбцу и, если найдет, то обновит ее (заменив старое имя на новое), а если не найдет, то создаст новую запись с новым именем.
Happy DAXing!

воскресенье, 13 ноября 2016 г.

Советы начинающим разработчикам DAX (часть 2)

Введение

В предыдущем посте я собрал советы по разработке с использованием интерфейса Dynamics AX. Материал для второй части посвящен советам по разработки в X++.

Группы полей таблиц

При использовании оператора select крайне рекомендуется указывать список необходимых полей в случае, когда в выборке требуются не все поля таблицы. Однако, запомните, что значения содержатся только в перечисленных полях. Если дальше в коде попытаться использовать другое поле, то результат будет далек от ожидаемого. 
В примере условие if будет всегда возвращать false, т.к. поле имеет значение null.

То же самое касается и exists/notexists join. В следующем запросе буфер записей custBankAccount не содержит данные.

Оператор where и индексы

При использовании оператора select в коде X++, используйте оператор where к полям, которые содержат индексы. Если это невозможно, то стоит рассмотреть вариант добавления в таблицу индекса, совпадающего с тем, что содержится в операторе where. Если содержимое за оператором where не соответствует индексам, то будут перебираться записи таблицы, что может серьезно отразиться на производительности, если в таблицах большое количество записей. Особенно это актуально для запросов к таблицам проводок.
Избегайте запросов, подобных указанному ниже, где таблица SalesTable не содержит индекса для поля DeliveryDate:

FirstOnly

В случаях проверки условия на истинность, используйте в запросах select слово firstonly для получения первой найденной записи из таблицы. Например, firstonly может использоваться для проверки есть ли в заказах на продажу необходимые строки, т.к. достаточно найти всего одну строку для подтверждения истинности. При использовании firstonly поиск прекратится сразу, как только будет найдена первая запись. См пример кода:

Блокировки

Если операторы ttsbegin и ttscommit используются внутри других операторов ttsbegin и ttscommit, то блокировки на записях таблицы будут держаться до тех пор, пока выполнение кода не дойдет до внешнего ttscommit, поэтому старайтесь помещать как можно меньше кода между ttsbegin и ttscommit. Кроме того, т.к. блокировки держатся до ttscommit, избегайте вызовов диалогов, требующих вмешательства пользователя, внутри tts-операторов.
В следующем примере кода блокировки вызываются обоими операторами update и эти блокировки не снимутся пока не будет достигнут внешний ttscommit:

Табличные методы

Прежде чем начинать писать код на доступ к таблице или ее обновлению, просмотрите методы этой таблицы. У большинства таблиц в AX уже есть встроенные методу, которые вы можете использовать для выполнения основных задач, таких, как поиск записей, получение значений полей и т.п.
Описание методов таблиц можно найти здесь: https://msdn.microsoft.com/en-us/library/aa852568.aspx

Классы в AX

Метод super() используется в наследовании. Если один класс наследуется от другого, то его называют дочерним, а исходный – родительским.
Например, класс InventMov_Sales наследуется от InventMovement.
Обратите внимание,  что префикс в имени класса часто указывает на родительский класс.
Дочерний класс имеет переопределенный метод canBeUpdatedRegistered. В него добавлена новая логика для определения является ли заказ на продажу продажей между компаниями, если да, то обновление не происходит. Если нет, тогда вызов super() выполнит тот же самый метод в исходном классе. См. скриншот ниже:
Будьте внимательны, когда добавляете код в переопределяемый метод, т.к. размещение кода до или после вызова super() может привести к разным результатам.

Поиск ошибок в AX

При обнаружении ошибок в AX может быть полезным добавление точек останова в метод Add в классе Info, т.к. все сообщения об ошибках проходят через этот метод:
Часто непонятно какой именно класс/метод выдает ошибку, с точкой останова в Info мы получаем доступ к стеку.

Перекрестные ссылки

Для начинающих осваивать Dynamics AX очень удобен такой инструмент как Перекрестные ссылки. Перекрестные ссылки позволяют видеть связи между объектами. Перекрестные ссылки очень удобны, когда в AX необходимо найти где используются расширенные типы данных, поля или методы. Это просто бесценный инструмент для быстрого поиска примеров кода в системе, кроме того он позволяет изучить и следовать стандартам программирования.

Запустить обновление перекрестных ссылок можно как из интерфейса AOT, так и через код.

Для запуска обновления перекрестных ссылок через AOT необходимо открыть форму обновления перекрестных ссылок (меню Сервис - Перекрестные ссылки - Периодические операции - Обновить):
Откроется форма обновления перекрестных ссылок, выберите опции и нажмите Ok
Описание опций:
Опция
Описание
Удалить все
Удалить существующую информацию по перекрестным ссылкам.
Обновить все
Обновить информацию по перекрестным ссылкам.
Выбрано
Обновить только определенную информацию по перекрестным ссылкам (для выбора обновляемой информации необходимо настроить фильтр по кнопке «Выбрать»).
Обновить модель данных
Обновление информации по перекрестным ссылкам для реверс-инжиниринга.

Для обновления через код создайте джоб:
Обновление перекрестных ссылок выполняется довольно долго. В моем случае это заняло 11 часов 30 минут.
Как только джоб отработает, у вас появится пункт Перекрестные ссылки:
Теперь можно найти, как и где используется выбранный метод, в примере ниже указано использование метода find для таблицы CustTable:

На этом все.
Happy DAXing!