Microsoft Dynamics AX
воскресенье, 23 сентября 2018 г.
AX2012: Работа с текстовыми файлами
TextIo
TextIo сохраняет текст в
кодировки Unicode.
Экспорт данных в текстовый файл с
помощью TextIo:
Чтение данных из текстового файла
с помощью TextIo:
TextBuffer
Помимо нативных средств для
работы с текстовыми файлами Dynamics AX предоставляет возможность вызывать
классы .NET для чтения/записи файлов, TextBuffer – один из таких классов.
Экспорт данных в текстовый файл с
помощью TextBuffer:
Чтение данных из текстового файла с помощью TextBuffer:
Happy DAXing!
понедельник, 17 сентября 2018 г.
AX2012: работа со складскими аналитиками
Попробуем разобраться с механизмом работы со складскими аналитиками в Dynamics AX2012.
В Dynamics AX доступны разные складские аналитики: сайт, склад, ячейка, паллет, партия, серийный номер и т.п. Эти характеристики применяются к номенклатурам.
Рассмотрим пример: у нас есть номенклатура с ItemId «ITEM001» для которой активны аналитики сайт, склад, ячейка, паллет, совершим складскую проводку на Сайт «S1», Склад «WHS01», Ячейку «LOC001», Паллет «PLT001». Для хранения проводки потребуется таблица с полями складских аналитик:
Попробуем все это упростить, создав новое поле ID, которое будет ссылаться на комбинацию этих значений. Например, «IDIM001» относится к Сайту «S1», Складу «WHS01», Ячейке «LOC001» и Паллету «PLT001». Кроме того, создадим отдельную таблицу для хранения значений всех этих 10 полей вместе с новым ID полем и добавим наше новое поле в таблицу проводок. Теперь все эти 100 записей мы можем сохранить лишь в одном ID поле.
Теперь больше не требуется хранить данные о 10 складских аналитиках в 100 записях, вместо этого нам нужно сохранить лишь одно поле, указывающее на определенную комбинацию этих складских аналитик. Пример таких таблиц можно увидеть ниже:
Такое поле ID в Dynamics AX называется InventDimId и таблица, в которой хранятся комбинации складских аналитик, а также это поле InventDimId называется InventDim:
Эта таблица играет крайне важную роль в Dynamics AX - каждая таблица, связанная со складскими аналитиками, как SalesLine, PurchLine, InventTrans - все имеют связь с InventDim.
Существуют уже готовые функции для создания и поиска InventDimId для различных комбинаций складских аналитик. Наиболее часто используется findOrCreate() для поиска складской аналитики - если InventDimId уже существует для заданной комбинации, тогда система вернет значение, если нет, то создаст новое значение и вернет его.
Если необходимо создать новые таблицы для хранения складских аналитик, то пользуйтесь уже готовым решением.
Happy DAXing!
Подсчет времени в X++
При выполнении некоторых времязатратных операций, например, чтение данных из Excel, выполнение запроса, выгрузка данных и т.п., бывает нужно знать длительность каждой операции – порой это помогает оптимизировать логику или запрос.
Для этой цели можно использовать функцию WinAPI::getTickCount():
WinAPI::getTickCount() возвращает значение в миллисекундах, данные необходимо приводить к необходимому виду – секунды, минуты.
Happy DAXing!
update_recordSet, insert_recordset и delete_from
update_recordSet, insert_recordset и delete_from
В Dynamics AX2012 уделено много внимания повышению производительности и система предоставляет инструменты для доступа (вставка, удаление, обновление) к базе данных с меньшими затратами (меньшим количеством обращений к БД), используя функционал SQL Server.
В AX 2012 есть несколько способов обновления записей в БД, один из них - последовательный перебор, используя while select. Но есть более эффективный способ - update_recordSet позволяет обновить совокупность записей за одно обращение к серверу.
Например, следующий запрос SQL:
можно в AX выполнить следующим образом:
update_recordSet позволяет использовать join:
Для вставки новых записей и удаления записей из таблиц без использования циклов используются функции delete_from:
и insert_recordset:
Happy DAXing!
воскресенье, 22 июля 2018 г.
SQL: Передача таблицы в хранимую процедуру
Введение
SQL Server 2005 и более ранние версии не поддерживают передачу табличных переменных в хранимую процедуру.
Рассмотрим в этом посте функционал SQL Server 2008 и более поздних версий, позволяющий передавать таблицу в хранимую процедуру или функцию.
Код
Перед созданием функции или хранимой процедуры, в которую будем передавать табличную переменную, нужно определить пользовательский табличный тип, который был введен в SQL Server 2008. Табличный тип представляет из себя табличную структуру.
Итак, первый этап – создание пользовательского табличного типа. Ниже в коде TSQL создается пользовательский табличный тип ItemInfo:
CREATE TYPE ItemInfo AS TABLE ( ItemId VARCHAR(50), Qty INT )
Для просмотра созданного типа можно использовать системное представление SYS.TYPE. Запрос ниже возвращает все определенные в системе типы:
Для вывода данных только по табличным типам этот запрос используется следующим образом:
Еще данные по табличным типам можно получить таким способом:
Структуру самой табличной переменной можно посмотреть следующим образом:
Итак, у нас есть необходимый табличный тип. Теперь создадим переменную с типом ItemInfo и попробуем вставить в нее несколько записей. Затем запросим данные из переменной для проверки корректности вставки:
Результат:
Теперь создадим хранимую процедуру, которой передадим табличную переменную – очень простую хранимую процедуру, которая принимает табличную переменную и выводит все ее содержимое:
Упс, получили ошибку:
Табличные переменные, передаваемые в хранимую процедуру или функцию, следует помечать как READONLY. Вызывающий объект не может изменять переданную ему таблицу. Ниже исправленный код:
Теперь выполним созданную хранимую процедуру - запустим следующий код:
Результат:
Как уже указывалось выше – нельзя изменять передаваемый в хранимую процедуру табличный параметр. Если попробовать изменить, то получим ошибку:
Ошибка:
SELECT * FROM SYS.TYPES
Для вывода данных только по табличным типам этот запрос используется следующим образом:
SELECT * FROM SYS.TYPES WHERE is_table_type = 1
Еще данные по табличным типам можно получить таким способом:
SELECT * FROM SYS.TABLE_TYPES
Структуру самой табличной переменной можно посмотреть следующим образом:
SELECT * FROM SYS.COLUMNS c JOIN SYS.TABLE_TYPES tt ON c.[object_id] = tt.type_table_object_id WHERE tt.name = 'ItemInfo'
Итак, у нас есть необходимый табличный тип. Теперь создадим переменную с типом ItemInfo и попробуем вставить в нее несколько записей. Затем запросим данные из переменной для проверки корректности вставки:
DECLARE @items AS ItemInfo INSERT INTO @items (ItemId, Qty) SELECT 'A0000001', 10 UNION ALL SELECT 'B0000001', 20 UNION ALL SELECT 'C0000001', 30 SELECT * FROM @items
Результат:
Теперь создадим хранимую процедуру, которой передадим табличную переменную – очень простую хранимую процедуру, которая принимает табличную переменную и выводит все ее содержимое:
CREATE PROCEDURE sp_SelectItemInfo ( @Items ItemInfo ) AS SELECT * FROM @Items
Упс, получили ошибку:
Табличные переменные, передаваемые в хранимую процедуру или функцию, следует помечать как READONLY. Вызывающий объект не может изменять переданную ему таблицу. Ниже исправленный код:
CREATE PROCEDURE sp_SelectItemInfo ( @Items ItemInfo READONLY ) AS SELECT * FROM @Items
Теперь выполним созданную хранимую процедуру - запустим следующий код:
DECLARE @Items AS ItemInfo INSERT INTO @items (ItemId, Qty) SELECT 'A0000010', 100 UNION ALL SELECT 'B0000010', 200 UNION ALL SELECT 'C0000010', 300 EXECUTE sp_SelectItemInfo @Items
Результат:
Как уже указывалось выше – нельзя изменять передаваемый в хранимую процедуру табличный параметр. Если попробовать изменить, то получим ошибку:
CREATE PROCEDURE sp_SelectItemInfo1 ( @Items ItemInfo READONLY ) AS SELECT * FROM @Items INSERT INTO @Items(ItemId, Qty) SELECT 'D0000010', 400
Ошибка:
Заключение
С табличными переменными работать довольно просто, важно только помнить, что нельзя менять/добавлять данные в получаемой переменной.четверг, 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!
Подписаться на:
Сообщения (Atom)