Сохранение вложенных сущностей
В этой статье рассматриваются способы сохранения сущности, имеющей, помимо скалярных полей, табличные данные. Например, поступление, которое имеет дату и номер, а также список товаров:

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

Есть два способа сохранения таких сущностей:
последовательный вызов двух команд, которые отдельно сохраняют скалярные и табличные данные;
создание на форме единого JSON-объекта, который отправляется на сервер одним запросом.
Две команды SaveCommand
Первый способ заключается в использовании двух последовательных команд. Первая сохраняет скалярные данные и возвращает на форму идентификатор добавленной записи. Вторая сохраняет табличные данные, используя результат выполнения первой команды, в котором хранится идентификатор новой записи.
<Command Name="SaveSequentialCommand" Type="SequentialCommand" Assembly="Commands">
<Commands>
<Command Name="IncomeInsertSaveCommand" />
<Command Name="IncomeItemDatabaseTableSaveCommand" />
</Commands>
</Command>
В примере рассматривается создание нового поступления и не описывается код для сохранения изменений в существующем поступлении.
Команда IncomeInsertSaveCommand вызывает SetDataConnection с запросом:
<SqlQuery Name="IncomeInsertSqlQuery">
<Text>
INSERT INTO template.income(
income_date,
income_number
)
VALUES (
{IncomeDate},
{IncomeNumber}
)
RETURNING income_id;
</Text>
</SqlQuery>
С необязательным предложением RETURNING
команда INSERT
возвращает значение колонки income_id для добавленной записи. Это значение вернется на форму и будет храниться в результате команды IncomeInsertSaveCommand.
Как вариант, вместо предложения RETURNING
можно использовать функцию currval
для получения текущего значения последовательности. Тогда запрос на сохранение скалярных данных будет иметь вид:
<SqlQuery Name="IncomeInsertSqlQuery">
<Text>
INSERT INTO template.income(
income_date,
income_number
)
VALUES (
{IncomeDate},
{IncomeNumber}
);
SELECT currval('template.income_id_seq'::regclass);
</Text>
</SqlQuery>
После того, как форма получила результат выполнения команды IncomeInsertSaveCommand, его можно использовать на форме, обращаясь к команде по имени.
Например, результат команды можно сохранить в параметр формы, вызвав команду ValueSetCommand после вызова команды IncomeInsertSaveCommand:
<Command Name="IncomeIdValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
<Parameter Name="IncomeId">
<Command Name="IncomeInsertSaveCommand" />
</Parameter>
</Command>
Или сразу использовать в DatabaseTableSetDataConnection для сохранения табличных данных, который вызывается во второй команде IncomeItemDatabaseTableSaveCommand:
<DataConnection Name="IncomeItemDatabaseTableSetDataConnection" Type="DatabaseTableSetDataConnection" Assembly="ComplexDataConnections">
<Workflow Name="Template" />
<DatabaseTable Name="IncomeItemDatabaseTable" />
<Parameters>
<Parameter NativeName="IncomeId">
<Command Name="IncomeInsertSaveCommand" />
</Parameter>
<Parameter NativeName="IncomeItemId">
<Column Name="IncomeItemId" />
</Parameter>
<Parameter NativeName="MaterialId">
<Column Name="MaterialId" />
</Parameter>
<Parameter NativeName="Quantity">
<Column Name="Quantity" />
</Parameter>
<Parameter NativeName="UnitPrice">
<Column Name="UnitPrice" />
</Parameter>
</Parameters>
<SqlQueries>
<SqlQuery Name="IncomeItemInsertSqlQuery" Type="Insert" />
<SqlQuery Name="IncomeItemUpdateSqlQuery" Type="Update" />
<SqlQuery Name="IncomeItemDeleteSqlQuery" Type="Delete" />
</SqlQueries>
</DataConnection>
В параметре IncomeId обращаемся к результату выполнения команды, а не вызываем ее повторно.
Недостаток такого варианта в том, что если команда сохранения табличных данных упадет с ошибкой, то изменения, внесенные в базу данных первой командой, все равно сохранятся. Таким образом, нарушится целостность данных.
JSON-объект
Второй способ заключается в создании на форме JSON-объекта и передаче его одним запросом на сервер.
Первое, что нужно сделать - создать объект Variable, в котором описать структуру Dictionary, которая позже сериализуется в JSON-объект:
<MyObject Name="IncomeDictionaryVariable" Type="Variable" Assembly="SimpleControls" ChangeForm="False">
<Value>
<Structure Type="Dictionary" Refresh="False">
<Key Name="common">
<Structure Type="Dictionary" Refresh="False">
<Key Name="income_id">
<DataTypeConvert Type="IntegerDataType">
<Parameter Name="IncomeId" Refresh="False" />
</DataTypeConvert>
</Key>
<Key Name="income_date">
<Object Name="IncomeDateDateTimePicker" Refresh="False" />
</Key>
<Key Name="income_number">
<Object Name="IncomeNumberTextBox" Refresh="False" />
</Key>
</Structure>
</Key>
<Key Name="income_item_list">
<Array>
<Source>
<Object Name="IncomeItemDatabaseTable" Refresh="False">
<Property Name="DictionaryArrayData" />
</Object>
</Source>
<Select>
<Items>
<Item Type="Field">IncomeItemId</Item>
<Item Type="Field">MaterialId</Item>
<Item Type="Field">Quantity</Item>
<Item Type="Field">UnitPrice</Item>
</Items>
</Select>
<ToDictionary>
<Key Name="income_item_id" Index="0" />
<Key Name="material_id" Index="1" />
<Key Name="quantity" Index="2" />
<Key Name="unit_price" Index="3" />
</ToDictionary>
</Array>
</Key>
</Structure>
</Value>
</MyObject>
Для списка позиций в поступлении используется конструкция <Array>
с преобразованием массива строк из таблицы DatabaseTable в массив словарей.
У тэгов <Object>
и <Parameter>
используется атрибут Refresh
, который определяет, будет ли обновляться значение у тэгов <Key>
и <Array>
, если изменится значение источника. Таким образом, значение объекта IncomeDictionaryVariable не будет обновляться каждый раз, когда изменяется какой-либо источник. Для ручного обновления IncomeDictionaryVariable нужно использовать команду ValueSetCommand для обращения к set-проперти Refresh у объекта Variable:
<Command Name="IncomeDictionaryVariableRefreshValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
<Object Name="IncomeDictionaryVariable">
<Property Name="Refresh" />
</Object>
</Command>
Для преобразования словаря к JSON-объекту используется команда SerializeToJsonCommand:
<Command Name="IncomeDictionaryVariableSerializeToJsonCommand" Type="SerializeToJsonCommand" Assembly="Commands">
<Variable>
<Object Name="IncomeDictionaryVariable" />
</Variable>
</Command>
Для передачи JSON-объекта на сервер используется команда IncomeSaveCommand типа SaveCommand, которая вызывает сохраняющее соединение:
<DataConnection Name="IncomeSaveSetDataConnection" Type="SetDataConnection" Assembly="DataConnections">
<Workflow Name="Template" />
<Parameters>
<Parameter NativeName="Model">
<Value>
<Command Name="IncomeDictionarySerializeToJsonCommand" />
</Value>
</Parameter>
</Parameters>
<SqlQueries>
<SqlQuery Name="IncomeSaveSqlQuery" Type="Update" />
</SqlQueries>
</DataConnection>
В единственный параметр Model передается результат команды сериализации словаря.
Сам запрос IncomeSaveSqlQuery будет иметь вид:
<SqlQuery Name="IncomeSaveSqlQuery">
<Text>
SELECT template.income_save({Model}::json);
</Text>
</SqlQuery>
Результат запроса будет храниться в результате команды IncomeSaveCommand, который можно получить по имени команды и сохранить в параметр формы, чтобы использовать его дальше на форме:
<Command Name="IncomeIdValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
<Parameter Name="IncomeId">
<Command Name="IncomeSaveCommand" />
</Parameter>
</Command>
Итоговая команда SaveSequentialCommand будет иметь вид:
<Command Name="SaveSequentialCommand" Type="SequentialCommand" Assembly="Commands">
<Commands>
<Command Name="IncomeDictionaryVariableRefreshCommand" />
<Command Name="IncomeDictionarySerializeToJsonCommand" />
<Command Name="IncomeSaveCommand" />
<Command Name="IncomeIdValueSetCommand" />
</Commands>
</Command>
Сначала обновляем переменную IncomeDictionaryVariable, а затем сериализуем ее в JSON-объект и передаем на сервер.
Преимущество этого варианта в том, что все данные отправляются на сервер одним запросом и обрабатываются в одной транзакции, что гарантирует целостность данных.
Last updated