Коллекции и InMemory таблицы в Dynamics Ax2012

В Dynamics Ax2012 есть несколько объектов для хранения данных в виде коллекции или таблицы в оперативной памяти.

Временные таблицы с типом InMemory

    В Dynamics Ax2012 появился новый тип временных таблиц InMemory. Ранее такого разделения не было. Данный тип таблиц создается в свободной оперативной памяти на уровне клиента или сервера, в зависимости на каком уровне выполняется процесс. Таблицы никогда не представляются в системе управления базами данных. Указывается тип таблицы в ее свойствах.

    Пока размер таблицы не превышает 128 KB, она хранится в памяти. После превышения размера, таблица переносится в файл на диск сервера. При этом файл именуется по соглашению, как $tmp<nnnnnnnn>.$$$

    Инициализация

    Инициализация таблицы происходит после вставки первой записи, и она продолжает существовать, пока существует переменная буфер записи на эту таблицу. Для операций вставки, обновления или удаления записей нет необходимости использовать транзакцию. Освобождение памяти и удаление файла с диска происходит, как только покидаем область, где объявлена переменная буфер записи ссылающая на таблицу.

    Добавление данных

    Для добавления данных в таблицу необходимо объявить переменную буфер записи и вызвать у нее метод insert.

    Пример

static void TableTmpInsertRecord(Args _args)
{
    TmpCustLedger custTmpLedger;

    custTmpLedger.Name = 'NameValue';
    custTmpLedger.Balance01 = 2345000;
    custTmpLedger.insert();
}

Чтобы вручную освободить память или удалить файл с диска, можно переменной буфера записи присвоить значение null.

    Пример

    custTmpLedger = null;

    В примере ниже выполняется копирование данных из таблицы CustTable во временную таблицу, которая создана с той же структурой что и у CustTable. Метод setTmp изменяет значение, возвращаемое методом getTableType, с TableType :: Regular на TableType :: InMemory.

    Пример

static void CopyPersistedTableToInMemoryJob(Args _args)
{
    CustTable custTable;
    CustTable custTmpLedger;

    custTmpLedger.setTmp();
    custTable.recordLevelSecurity(true);
    while select * from custTable where custTable.AccountNum like '1*'
    {
        custTmpLedger.data(custTable.data());
        custTmpLedger.doInsert();
        info(strFmt("Inserted a row for AccountNum = %1",custTable.AccountNum));
    }

    custTmpLedger = null;
}

    Индексы могут быть определены в таблице InMemory так же, как и для обычной таблицы. Если таблица InMemory создана как копия обычной таблицы, то индексы также копируются. Индексы необходимы для более быстрого получения данных, особенно, если таблица была записана в файл на диске.

Таблица InMemory и контейнеры

    Dynamics Ax2012 поддерживает специальных тип данных, который называет контейнер. Этот тип данных может быть использован как таблица InMemory.

    Данные в контейнере хранятся и извлекаются последовательно, но для таблицы можно настроить индексы, которые позволят увеличить скорость получения данных. Следует учитывать, что индекс не будет приносить пользы, если работа ведется с небольшим количеством записей. В таких случаях, использование контейнера будет требовать меньше накладных расходов и работать быстрее, чем таблица.

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


Контейнеры

    В Х++ контейнер не является классом. Это тип данных, который хранит последовательно отсортированные примитивные типы данных или другие контейнеры.

    Контейнер также может хранится в базе данных. Это один из типов значений, который можно выбрать при добавлении полей в таблицу.

    Контейнер чем-то похож на массив или на коллекцию такую, как List. Тем не менее, нельзя изменить размер или содержимое контейнера после его создания. Операторы в Х++, которые призваны модифицировать контейнер, на самом деле приводят к созданию нового контейнера и копированию значений в случае необходимости. Даже назначение контейнера другой переменной с типом контейнер приводит к созданию нового контейнера и копированию в него всех значений. Все эти внутренние манипуляции имеют последствия для производительности.

    Производительность контейнеров

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

    += выполняется быстрее чем conIns

    Если вы хотите создать новый контейнер, добавив новые данные, вы можете использовать либо оператор «+=», либо функцию «conIns». Оператор «+=» является более быстрой альтернативой. Функцию «conIns» необходимо использовать только тогда, когда необходимо добавить данные не в конец контейнера.

    Различия между контейнером и списком

    Класс List при создании, выделяет места больше, чем изначально необходимо. Выделенное место постепенно заполняется при добавлении элементов. Это более эффективно, чем выделать дополнительно место при каждой вставке данных. Обновление List выполняется быстрее, чем аналогичные операции с контейнером.

    При создании объекта List, необходимо определить один тип данных, который может хранить объект List. Это ограничение менее гибко, чем для контейнера. Однако можно хранить объекты в списке, тогда как контейнер может хранить только значения.

    Различия между контейнером и массивом

    Массив может хранить только элементы типа, определенного при создании объекта. Можно выделить пространство памяти для массива и заполнять это пространство со значениями позже, например, в цикле. Это эффективно работает.

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

Классы коллекций

    Язык Х++ предоставляет два составных типа: массивы и контейнеры. Они полезны для агрегирования значений более простых типов. Однако, нельзя хранить объекты в массивах или контейнерах. Поэтому Microsoft Dynamics Ax имеет коллекцию классов спроектированных для хранения объектов. Классы реализуются на C++, чтобы достичь максимальной производительности, и они являются системными классы.

    Классами коллекция являются: Array, List, Map, Set, Struct.

    Класс Set

    Это коллекция, которая может принимать значения любого типа данных, который есть в системе. Но отдельная коллекция может содержать только один тип данных, заданный при инициализации класса.

    Набор содержит только уникальные значения. Попытка добавить уже существующее значение будет проигнорирована. Чтобы узнать был ли добавлен элемент в коллекцию, необходимо проверить результат метода add. Данный метод возвращает истину, если элемент был добавлен или ложь, если элемент уже существовал в коллекции.

    При вставке нового элемента, коллекция автоматически сортируется.

    Также данный класс позволяет объединять коллекции, находить отличия, а также пересечения между двумя коллекциями.

    

    Пример

    Set set1 = new Set(Types::String);
    Set set2 = new Set(Types::String);

    set1.add('a');
    set1.add('b');
    set1.add('c');
    set2.add('c');
    set2.add('d');
    set2.add('e');

    print Set::union(set1, set2).toString(); 
    //{a, b, c, d, e}

    print Set::intersection(set1, set2).toString(); 
    // {c}

    print Set::difference(set1, set2).toString(); 
    //{a, b}

    print Set::difference(set2, set1).toString(); 
    //{d, e}

    Класс List

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

    Пример

    List list = new List(Types::Integer);

    list.addEnd(100);
    list.addEnd(200);
    list.addEnd(100);
    list.addStart(300);
    print list.toString(); // 300, 100, 200, 100
    print list.elements(); // 4

    Класс Map    

    Карта соответствия представляет собой коллекцию пар «ключ-значение». Ключ служит уникальным идентификатором значения. Но разным ключам может соответствовать одинаковое значение. Ключом и значением может быть любой тип данных, и класс, и запись. Если в карту будет добавляться пара, и ключ уже будет существовать, то хранимое значение будет заменено вновь добавляемым.

    Пример

    Map map = new Map(Types::String, Types::Enum);
    Word wordType;

    map.insert("Car", Word::Noun);
    map.insert("Bike", Word::Noun);
    map.insert("Walk", Word::Verb);
    map.insert("Nice", Word::Adjective);
    print map.elements(); //4;
    wordType = map.lookup("Car");

Метод lookup позволяет получить значение по ключу, не прибегаю к последовательному перебору всех элементов. Но следует помнить, что если элемент не существует, то вызов данного метода приведет к вызову исключительно ошибки, поэтому необходимо проверить существование ключа. Для этого необходимо использовать метод exists.

Пример

    if (map.exists("Car"))
    wordType = map.lookup("Car");
    if (map.exists("Car"))
    wordType = map.lookup("Car");

    Перебор элементов в коллекции

    Чтобы последовательно перебрать все элементы в коллекции необходимо использовать енумератор (enumerator,) или итератор (iterator). Когда коллекции впервые появились в Dynamics AX, итератор был единственно возможным вариантом. Но из-за некоторых появившихся недостатков, которые проявляются в виде труднонаходимых ошибок, были добавлены енумераторы, а итераторы были сохранены для обратной совместимости. Чтобы подчеркнуть тонкие различия, следующий код показывает, как перебрать коллекцию с использованием обоих подходов.

    Пример

{
    ListIterator   iterator;
    ListEnumerator enumerator;
    List           list = new List(Types::Integer);

    //Traverse using an iterator.
    iterator = new ListIterator(list);

    while (iterator.more())
    {
        print iterator.value();
        iterator.next();
    }

    //Traverse using an enumerator.
    enumerator = list.getEnumerator();
    while (enumerator.moveNext())
    {
        print enumerator.current();
    }
}

    Различия

    Первое различие заключается в том, каким образом создаются итератор и енумератор. Для итератора вызывается метод new, когда как енумератор создается из класса-коллекции, вызовом метода getEnumerator. В большинстве случаев оба подхода будут работать одинаково верно. Однако, когда коллекция находится на уровне, противоположном тому, на котором производится перебор элементов, ситуация совершенно иная. Например, если коллекция находится на уровне клиента, а перебирается на уровне сервера, применение итератора становится невозможным, поскольку итератор не поддерживает межуровневые ссылки. Енумератор также не поддерживает межуровневые ссылки, но ему это и не требуется, поскольку он создается на том же уровне, что и коллекция. Перебор коллекции на уровне сервера с использованием енумератора на уровне клиента вызывает довольно интенсивную нагрузку на сеть, но результат получается логически правильным. Во многих случаях трудно отследить подобные ошибки, так как они проявляются только в том случае, если операция выполняется в пакетном режиме.

    В предыдущих версиях Dynamics AX эта проблема была даже более явной, поскольку разработка и тестирование производились в двухуровневой среде, и проблема проявлялась только при переходе к трехуровневой среде.

    Второе различие между итераторами и енумераторами проявляется в том, как указатель перебора коллекции (traversing pointer) продвигается вперед. В случае итератора требуется явно вызывать пару методов: more и next. В случае енумератора — только один метод moveNext. Большинство разработчиков хотя бы раз невольно попадали в ситуацию с бесконечным циклом, потому что забывали перевести указатель.

    Если вы всегда будете использовать енумератор, то не испытаете вышеописанных проблем. Единственной ситуацией, в которой невозможно избежать использования итератора, является удаление элементов из коллекции List.

    Измерения

    Когда вставляется значение табличного типа в коллекцию, выполняется операция копирования этого значения в память. И хотя табличные типы в X++ представляют собой ссылки, они ведут себя как значения при вставке в коллекции.

    Проведем сравнение скорости заполнения коллекции, в данном случае Set, элементами типа класс и запись.

    Пример

    //Struct
    setT = new Set(Types::Class);
    for (idx = 1; idx <= 5000; idx++)
    {
        pointStruct = new Struct("Real v1; Real v2");
        pointStruct.value("v1", idx);
        pointStruct.value("v2", idx);
        setT.add(pointStruct);
    }

    //Table
    setT = new Set(Types::Record);
    for (idx = 1; idx <= 5000; idx++)
    {
        tmp.Num = idx;
        tmp.LineNum = idx;
        setT.add(tmp);
    }

    /*
    Struct
    0.31 sec.

    Table InMemory
    0.06 sec.
    */

    Как видно из результатов, времени на создание класса, для добавления в коллекцию, требуется гораздо больше, чем создание элемента на основе временной таблицы.

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

    Пример

    for (idx = 1; idx <= 5000; idx++)
    {
        setT.add(strFmt("%1", idx));
    }

    for (idx = 1; idx <= 5000; idx++)
    {
        mapT.insert(idx, strFmt("%1", idx));
    }    

    for (idx = 1; idx <= 5000; idx++)
    {
        tmp.Num = idx;
        tmp.Id = strFmt("%1", idx);
        tmp.doInsert();
    }

    /*
    Set
    0.03 sec.

    Map
    0.02 sec.

    Table InMemory
    0.73 sec.
    */

 

    Также стоит не забывать, что все коллекции поддерживают сериализацию. У класса коллекции есть метод pack, который генерирует контейнер. Особенно необходима сериализация, для передачи информации между уровнями, клиентом и сервером. После получении контейнера на противоположном уровне, из него необходимо создать новый объект с инициализацией переменных. Для этого необходимо воспользоваться методом unpack.

InMemory Tables

Containers

Collection

Comments

So empty here ... leave a comment!

Добавить комментарий

Sidebar