Урок 26. Работа с JSON на форме

В прошлом уроке мы познакомились с возможностями PostgreSQL по работе с JSON-объектами, когда сохраняли заказ и клиента через API-запросы. Платформа Workflow Technology так же поддерживает работу с JSON-объектами, что позволят собирать на форме всю информацию и единым запросом передавать ее на сервер. Это избавляет нас от необходимости создавать черновую запись для работы с сущностями со сложной иерархической структурой (например, сущность заказа).

circle-check

Работа с JSON на форме

Работу с JSON будем рассматривать на примере карточки заказа, так как заказ имеет вложенные сущности и потенциально может иметь больше уровней вложенности. Ранее на примере заказа мы рассматривали паттерн Add/Edit. Теперь будет интересно увидеть, как изменится форма с использованием JSON-объекта.

Построение объекта

С формы заказа на сервер должен уходить JSON-объект подобного вида:

{
   "order_id":null,
   "client_id":7,
   "order_number":"123",
   "order_date":"2022-03-14 10:00:00Z",
   "description":"Сохранение через JSON",
   "order_position":[
      {
         "order_position_id":null,
         "material_id":4,
         "quantity":25.00,
         "unit_price":200.00
      },
      {
         "order_position_id":null,
         "material_id":2,
         "quantity":2.00,
         "unit_price":350.00
      }
   ],
   "order_payment":[
      {
         "order_payment_id":null,
         "cash_id":null,
         "date":"2022-03-14 10:00:00Z",
         "account_id":4,
         "summ":900.00
      }
   ]
}

Так как JSON-объект представляет собой набор пар "ключ-значение", то нам необходимо создать структуру <Structure>arrow-up-right типа Dictionaryarrow-up-right, которую присвоим в объект Variablearrow-up-right:

Обратите внимание, что у тэга <MyObject> стоит атрибут ChangeFormarrow-up-right со значением False.

Добавим необходимые ключи в структуру и укажем источники данных:

Для полей с массивами объектов используем конструкцию <Array>arrow-up-right с преобразованием массива строк из таблицы в массив словарей.

У тэгов <Object> и <Parameter> появился атрибут Refresh, который определяет, будет ли обновляться значение у тэгов <Key> и <Array>, если изменится значение источника. Таким образом, значение объекта OrderDictionaryVariable не будет пересчитываться каждый раз, когда измениться какой-либо источник. Для ручного пересчета OrderDictionaryVariable нужно использовать команду ValueSetCommandarrow-up-right для вызова set-проперти Refresharrow-up-right у объекта Variable:

Чтобы словарь преобразовать к JSON-объекту, создадим команду типа SerializeToJsonCommandarrow-up-right:

circle-info

Команда SerializeToJsonCommand при формировании JSON-объекта преобразует все даты со временем к UTC относительно пользовательских настроек временной зоны.

Добавим вызов обеих команд в SaveSequentialCommand.

Переделаем соединение с данными OrderUpdateSetDataConnection, убрав все параметры и добавив один параметр Model, в который будет передаваться результат команды сериализации объекта в JSON. Переименуем OrderUpdateSetDataConnection в OrderSaveSetDataConnection:

Также переименуем команду OrderUpdateSaveCommand в OrderSaveCommand.

Перейдем в серверный xml-файл (Template.xml). Скорректируем текст запроса OrderUpdateSqlQuery и переименуем его в OrderSaveSqlQuery:

Самостоятельно

Удаление логики черновых записей

Удалите всю логику, связанную с черновыми записями заказов на главной форме и в карточке заказа.

Удалите колонку added из таблицы template.order и всех запросов, которые ее проверяют или обновляют.

Правка сохранения изменений

Удалите запросы OrderPositionInsertSqlQuery и OrderPositionUpdateSqlQuery и связанные с ними команды. В карточке позиции заказа создайте параметры формы MaterialId, Quantity и UnitPrice. Через эти параметры будем передавать значения между родительской формой и карточкой позиции. Пока можете не реализовывать добавление значений в таблицу на родительской форме - в следующем разделе мы сделаем это вместе.

Удалите запрос OrderPositionByIdSelectSqlQuery и OrderPositionPrimaryGetDataConnection. Объекты MaterialComboBox, QuantityNumericBox и UnitPriceNumericBox заполняйте значениями из параметров формы.

Удаление записей также рассмотрим дальше в уроке.

Аналогично сделайте для оплат в заказе.

Сохранение в базу

Реализуйте функцию template.order_save(json). Функция должна возвращать order_id, который необходимо отлавливать на форме TemplateOrderEdit.xml и писать в параметр OrderId.

Не забывайте про работу с датой со временем и временные зоны. В JSON-строке на сервер даты со временем придут в UTC. Для приведения времени во временную зону сервера используйте функцию public.convert_date_json(timestamp without time zone).

Хранение данных на форме

Удаление записей

Вернемся в файл TemplateOrderEdit.xml и на примере позиций заказа рассмотрим, как реализовать удаление через JSON-объект.

Удалим запрос OrderPositionDeleteSqlQuery и связанные с ним соединение с данными OrderPositionDeleteDataConnection и команду.

Добавим преобразующее загружающее соединение с данными типа ConvertDataConnectionarrow-up-right:

С его помощью мы дополняем исходный набор данных из соединения OrderPositionPrimaryGetDataConnection колонкой Deleted со значением False по умолчанию. С OrderPositionConvertDataConnection мы можем работать так же, как с любым другим загружающим DataConnection. Например, мы можем использовать его в качестве источника данных для объектов или другого DataConnection.

Создадим SecondaryGetDataConnection, чтобы отфильтровать записи по колонке Deleted:

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

Теперь займемся удалением записей из OrderPositionConvertDataConnection. Важно то, что в нем могут быть записи из таблицы в базе данных (у таких записей будет значение в поле OrderPositionId) и новые записи, добавленные на форме. Для первых мы должны обновлять значение в поле Deleted, а для вторых будем использовать set-проперти DeleteRowsByIndices.

Первым делом создадим условие IsNullCondition для проверки OrderPositionId:

Если запись не имеет идентификатора, то мы будем удалять ее из OrderPositionConvertDataConnection:

Если запись имеет идентификатор, то мы меняем значение в колонке Deleted:

Скорректируем Execution на удаление позиции заказа:

Скорректируем OrderDictionaryVariable, заменив источник значения для ключа order_position и добавив колонку Deleted:

Аналогичным образом измените логику работы с оплатами в заказе. Для хранения данных об оплатах в карточке заказа создайте преобразующее загружающее соединение с данными OrderPaymentConvertDataConnection.

Скорректируйте функцию template.order_save(json) так, чтобы в ней обрабатывалось поле deleted у объектов из order_position и order_payment.

Добавление и изменение записей

Добавление и изменение записей будем рассматривать с заделом на несколько уровней вложенности. Отдельную сложность представляют сущности третьего и более уровней вложенности.

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

Временный идентификатор

Для таких целей можно использовать объект типа CounterVariablearrow-up-right, который является счетчиком положительных целых чисел. При обращении счетчик автоматически увеличивает значение на один и возвращает это значение:

Переменная CounterCopyVariable необходима для сохранения текущего значения счетчика:

В OrderPositionConvertDataConnection и OrderPaymentConvertDataConnection добавляем поле ID, которое будет хранить временный идентификатор, и укажем в качестве значения объект CounterVariable:

После загрузки формы и получении данных в PrimaryGetDataConnection оба ConvertDataConnection построят свои внутренние таблицы, и в каждой строчке в поле ID будут проставлены уникальные значения.

В таблицы OrderPositionDatabaseTable и OrderPaymentDatabaseTable добавим колонки ID.

Создадим команды ValueSetCommand для добавления и изменения записей в OrderPositionConvertDataConnection. Для этого будем использовать set-проперти AddRow и UpdateRow у DataConnection.

Добавление записи в ConvertDataConnection

В команде на добавление записей будем обращаться к CounterCopyVariable для заполнения колонки ID уникальным временным идентификатором:

Для новой строки в колонке Deleted сразу прописываем значение False.

Добавим команду, с помощью которой будем сохранять текущее значение переменной-счетчика:

Скорректируем команду для выделения новой строки в таблице:

Так как временный идентификатор является единственным уникальным ключом, по которому мы можем получить конкретную запись, то по нему и будем выделять строку в таблице. В команде используем конструкцию <Input>arrow-up-right, что позволит передавать нужное значение в момент вызова команды. Так мы сможем использовать значение переменной CounterCopyVariable, если создавали новую позицию заказа. А при редактировании позиции заказа будем использовать значение временного идентификатора редактируемой записи.

Скорректируем Execution, который отрабатывает результат выполнения команды OrderPositionAddFormShowCommand:

Изменение записи в ConvertDataConnection

Создадим команду обновления строки:

Обновление данных в строке происходит по ее индексу, который мы можем получить с помощью get-проперти RowIndexOfarrow-up-right по уникальному значению, которым является временный идентификатор ID. Значение временного идентификатора берем из выделенной строки в таблице OrderPositionDatabaseTable, так как карточка позиции заказа открывается в модальном режиме. В противном случае, нам пришлось бы передавать на дочернюю форму временный идентификатор и после получать его значение через параметр команды OrderPositionEditFormShowCommand.

Скорректируем Execution, который отрабатывает результат выполнения команды OrderPositionEditFormShowCommand:

Здесь так же можно было добавить команду выделения редактируемой строки, если бы команда OrderPositionEditFormShowCommand открывала окно в немодальном режиме.

Аналогичным образом измените логику добавления и редактирования оплат в заказе.

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

После внесенных изменение команды OrderPositionDataConnectionRefreshCommand и OrderPaymentDataConnectionRefreshCommand не используются - их можно удалить. Но обновлять соединения с данными для позиций заказа и оплат необходимо, чтобы подтягивать из базы данных на форму идентификаторы новых записей. Так как после сохранения форма остается открытой, и пользователь может редактировать новые записи, то важно иметь оригинальные идентификаторы, чтобы новые изменения правильно сохранялись в базу данных.

Создадим новую команду типа DataConnectionRefreshCommand:

Так же необходимо создать команду, сохраняющую идентификатор нового заказа в параметр формы:

Раньше в параметре OrderId всегда было значение, так как при открытии формы на создание заказа, на родительской форме создавался черновик заказа, идентификатор которого передавали в параметр. Теперь мы удалили логику черновиков, и параметр OrderId будет иметь пустое значение при создании заказа. А так как после сохранения изменений форма остается открытой, то повторное сохранение изменений будет приводить к дублированию записей в базе данных. Чтобы этого не происходило, мы и будем сохранять идентификатор нового заказа.

Добавим новые команды в команду SaveSequentialCommand:

В соединение с данными OrderPrimaryGetDataConnection добавим атрибут RefreshQuery="False" на параметр OrderId, чтобы DataConnection не обновлялся автоматически при изменении параметра формы OrderId. Такие же изменения внесем и в OrderPositionPrimaryGetDataConnection и OrderPaymentPrimaryGetDataConnection - эти соединения обновляем вручную.

Запустите приложение и проверьте работу формы заказа.

Вложенные сущности третьего уровня

Если у позиции заказа будут вложенные сущности, то для их хранения будет использоваться отдельный ConvertDataConnection, например:

В поле OrderPositionID типа Substitution будет подставляться значение из поля ID в OrderPositionConvertDataConnection на основе полей OrderPositionId в обоих соединениях.

Обратите внимание, что соединение с данными PositionItemConvertDataConnection имеет вложенный тэг <ManualRefresh>arrow-up-right со значением True. При этом источник данных (PositionItemPrimaryGetDataConnection) может не иметь вложенный тэг <ManualLoad>arrow-up-right со значением True. Следовательно, необходимо создавать команду DataConnectionRefreshCommand для его обновления.

Команда добавление записей в PositionItemConvertDataConnection может иметь вид:

Объект PositionItemArrayToAddVariable будет содержать массив объектов (точнее, матрицу значений), которые описывают вложенные сущности позиции заказа. Такой массив будет возвращаться с дочерней формы (карточки позиции заказа). С помощью конструкции <Array>arrow-up-right дополним матрицу новыми значениями для полей ID, OrderPositionID и Deleted. Все строки в матрице будут иметь уникальные значения в колонке ID.

В момент вызова функции в качестве значения <Input Name="OrderPositionID"> будем передавать либо значение CounterCopyVariable, которое сохранили перед добавлением новой позиции заказа, либо значение параметра OrderPositionID из команды OrderPositionEditFormShowCommand.

Итоги

В этом уроке мы рассмотрели альтернативу паттерну Add/Edit, который строится на создании черновой записи и работе с этим черновиком в карточке сущности. Плюсом работа с JSON является возможность передавать данные сущности со сложной иерархической структурой единым запросом, что сохраняет целостность информации в базе данных. Минусом - такой подход усложняет работу с данными на формах, так как необходимо передавать их с родительской формы на дочернюю и обратно, а также следить за связанностью данных при хранении их на форме. И сам SQL-запрос на сохранение в базу данных становится громоздким и сложным.

Ответы

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

file-archive
604KB

Last updated