воскресенье, 23 сентября 2018 г.

AX2012: Работа с файлами csv


Для работы с файлами *.csv воспользуемся классом CommaIo – используется для создания файлов с разделителями (по умолчанию разделителями являются запятые).

Экспорт данных в csv-файл:


Чтение данных из csv-файла:



Happy DAXing!

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. Запрос ниже возвращает все определенные в системе типы:

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!