Урок 26. Работа с JSON на форме
Last updated
Last updated
В прошлом уроке мы познакомились с возможностями PostgreSQL по работе с JSON-объектами, когда сохраняли заказ и клиента через API-запросы. Платформа Workflow Technology так же поддерживает работу с JSON-объектами, что позволят собирать на форме всю информацию и единым запросом передавать ее на сервер. Это избавляет нас от необходимости создавать черновую запись для работы с сущностями со сложной иерархической структурой (например, сущность заказа).
Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье .
При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела прошлого урока. Скопируйте папки Forms, Workflow и Patterns в папку с развернутым проектом, например, в папку D:\WT\Projects\Template\Projects\1. Template.
Инструкция по подключению шаблонов находится по .
Работу с JSON будем рассматривать на примере карточки заказа, так как заказ имеет вложенные сущности и потенциально может иметь больше уровней вложенности. Ранее на примере заказа мы рассматривали . Теперь будет интересно увидеть, как изменится форма с использованием JSON-объекта.
С формы заказа на сервер должен уходить JSON-объект подобного вида:
Добавим необходимые ключи в структуру и укажем источники данных:
Добавим вызов обеих команд в 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 и команду.
С его помощью мы дополняем исходный набор данных из соединения 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.
Добавление и изменение записей будем рассматривать с заделом на несколько уровней вложенности. Отдельную сложность представляют сущности третьего и более уровней вложенности.
В таких случаях на формах необходимо использовать временные идентификаторы, по которым при сохранении в базу данных будут восстанавливаться отношения сущностей между собой.
Переменная CounterCopyVariable необходима для сохранения текущего значения счетчика:
В OrderPositionConvertDataConnection и OrderPaymentConvertDataConnection добавляем поле ID, которое будет хранить временный идентификатор, и укажем в качестве значения объект CounterVariable:
После загрузки формы и получении данных в PrimaryGetDataConnection оба ConvertDataConnection построят свои внутренние таблицы, и в каждой строчке в поле ID будут проставлены уникальные значения.
В таблицы OrderPositionDatabaseTable и OrderPaymentDatabaseTable добавим колонки ID.
Создадим команды ValueSetCommand для добавления и изменения записей в OrderPositionConvertDataConnection. Для этого будем использовать set-проперти AddRow и UpdateRow у DataConnection.
В команде на добавление записей будем обращаться к CounterCopyVariable для заполнения колонки ID уникальным временным идентификатором:
Для новой строки в колонке Deleted сразу прописываем значение False.
Добавим команду, с помощью которой будем сохранять текущее значение переменной-счетчика:
Скорректируем команду для выделения новой строки в таблице:
Скорректируем Execution, который отрабатывает результат выполнения команды OrderPositionAddFormShowCommand:
Создадим команду обновления строки:
Скорректируем Execution, который отрабатывает результат выполнения команды OrderPositionEditFormShowCommand:
Здесь так же можно было добавить команду выделения редактируемой строки, если бы команда OrderPositionEditFormShowCommand открывала окно в немодальном режиме.
Аналогичным образом измените логику добавления и редактирования оплат в заказе.
После внесенных изменение команды OrderPositionDataConnectionRefreshCommand и OrderPaymentDataConnectionRefreshCommand не используются - их можно удалить. Но обновлять соединения с данными для позиций заказа и оплат необходимо, чтобы подтягивать из базы данных на форму идентификаторы новых записей. Так как после сохранения форма остается открытой, и пользователь может редактировать новые записи, то важно иметь оригинальные идентификаторы, чтобы новые изменения правильно сохранялись в базу данных.
Создадим новую команду типа DataConnectionRefreshCommand:
Так же необходимо создать команду, сохраняющую идентификатор нового заказа в параметр формы:
Раньше в параметре OrderId всегда было значение, так как при открытии формы на создание заказа, на родительской форме создавался черновик заказа, идентификатор которого передавали в параметр. Теперь мы удалили логику черновиков, и параметр OrderId будет иметь пустое значение при создании заказа. А так как после сохранения изменений форма остается открытой, то повторное сохранение изменений будет приводить к дублированию записей в базе данных. Чтобы этого не происходило, мы и будем сохранять идентификатор нового заказа.
Добавим новые команды в команду SaveSequentialCommand:
В соединение с данными OrderPrimaryGetDataConnection добавим атрибут RefreshQuery="False" на параметр OrderId, чтобы DataConnection не обновлялся автоматически при изменении параметра формы OrderId. Такие же изменения внесем и в OrderPositionPrimaryGetDataConnection и OrderPaymentPrimaryGetDataConnection - эти соединения обновляем вручную.
Запустите приложение и проверьте работу формы заказа.
Если у позиции заказа будут вложенные сущности, то для их хранения будет использоваться отдельный ConvertDataConnection, например:
В поле OrderPositionID типа Substitution будет подставляться значение из поля ID в OrderPositionConvertDataConnection на основе полей OrderPositionId в обоих соединениях.
Команда добавление записей в PositionItemConvertDataConnection может иметь вид:
В момент вызова функции в качестве значения <Input Name="OrderPositionID">
будем передавать либо значение CounterCopyVariable, которое сохранили перед добавлением новой позиции заказа, либо значение параметра OrderPositionID из команды OrderPositionEditFormShowCommand.
В архиве присутствуют xml-файлы форм и серверный xml-файл, также лежит бэкап базы данных и файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.
Так как JSON-объект представляет собой набор пар "ключ-значение", то нам необходимо создать структуру типа , которую присвоим в объект :
Обратите внимание, что у тэга <MyObject>
стоит атрибут со значением False.
Для полей с массивами объектов используем конструкцию с преобразованием массива строк из таблицы в массив словарей.
У тэгов <Object>
и <Parameter>
появился атрибут Refresh
, который определяет, будет ли обновляться значение у тэгов <Key>
и <Array>
, если изменится значение источника. Таким образом, значение объекта OrderDictionaryVariable не будет пересчитываться каждый раз, когда измениться какой-либо источник. Для ручного пересчета OrderDictionaryVariable нужно использовать команду для вызова set-проперти у объекта Variable:
Чтобы словарь преобразовать к JSON-объекту, создадим команду типа :
Добавим преобразующее загружающее соединение с данными типа :
Для таких целей можно использовать объект типа , который является счетчиком положительных целых чисел. При обращении счетчик автоматически увеличивает значение на один и возвращает это значение:
Так как временный идентификатор является единственным уникальным ключом, по которому мы можем получить конкретную запись, то по нему и будем выделять строку в таблице. В команде используем конструкцию , что позволит передавать нужное значение в момент вызова команды. Так мы сможем использовать значение переменной CounterCopyVariable, если создавали новую позицию заказа. А при редактировании позиции заказа будем использовать значение временного идентификатора редактируемой записи.
Обновление данных в строке происходит по ее индексу, который мы можем получить с помощью get-проперти по уникальному значению, которым является временный идентификатор ID. Значение временного идентификатора берем из выделенной строки в таблице OrderPositionDatabaseTable, так как карточка позиции заказа открывается в модальном режиме. В противном случае, нам пришлось бы передавать на дочернюю форму временный идентификатор и после получать его значение через параметр команды OrderPositionEditFormShowCommand.
Обратите внимание, что соединение с данными PositionItemConvertDataConnection имеет вложенный тэг со значением True. При этом источник данных (PositionItemPrimaryGetDataConnection) может не иметь вложенный тэг со значением True. Следовательно, необходимо создавать команду DataConnectionRefreshCommand для его обновления.
Объект PositionItemArrayToAddVariable будет содержать массив объектов (точнее, матрицу значений), которые описывают вложенные сущности позиции заказа. Такой массив будет возвращаться с дочерней формы (карточки позиции заказа). С помощью конструкции дополним матрицу новыми значениями для полей ID, OrderPositionID и Deleted. Все строки в матрице будут иметь уникальные значения в колонке ID.
В этом уроке мы рассмотрели альтернативу , который строится на создании черновой записи и работе с этим черновиком в карточке сущности. Плюсом работа с JSON является возможность передавать данные сущности со сложной иерархической структурой единым запросом, что сохраняет целостность информации в базе данных. Минусом - такой подход усложняет работу с данными на формах, так как необходимо передавать их с родительской формы на дочернюю и обратно, а также следить за связанностью данных при хранении их на форме. И сам SQL-запрос на сохранение в базу данных становится громоздким и сложным.