Урок 15. Режимы загрузки данных

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

При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела Ответы прошлого урока. Скопируйте папки Forms, Workflow и Patterns в папку с развернутым проектом, например, в папку D:\WT\Projects\Template\Projects\1. Template.

Инструкция по подключению шаблонов находится по ссылке.

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

Платформа Workflow Technology поддерживает три режима загрузки данных:

Обеспечивает минимальную скорость загрузки форм, при этом требует минимальное количество ресурсов со стороны сервера.

Обеспечивает стандартную скорость загрузки форм, при этом не требует значительного количества ресурсов со стороны сервера.

Обеспечивает увеличенную скорость загрузки форм, однако при этом требует максимальное количество ресурсов со стороны сервера. Режим по умолчанию.

Эти режимы описаны в таблице public.load_mode, а в поле selected этой таблицы можно выбирать текущий режим.

Эти режимы работают только в момент открытия формы и первичной загрузки данных в PrimaryGetDataConnection, не имеющих тэга <ManualLoad> со значением True. Еще они не влияют на работу команды DataConnectionRefreshCommand.

Платформа при загрузке формы строит дерево зависимостей для запросов в DataConnection (в одном DataConnection может быть несколько запросов). При этом учитываются все связи между ними (прямые и косвенные через команды и объекты). На основе этого дерева строится порядок выполнения запросов. Первыми всегда будут выполняться те запросы, которые не зависят от результата выполнения других запросов.

В разделе Режим загрузки данных (LoadMode) на примере карточки заказа подробнее рассмотрим работу каждого режима. Но, сначала скорректируем форму настроек, добавив возможность из интерфейса программы менять режим загрузки данных на формы, и познакомимся с утилитой WorkflowVisualizer, с помощью которой сможем отследить изменение порядка загрузки данных при переключении режима.

Форма настроек

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

Очень удобно, когда пользователь имеет возможность самостоятельно выбирать режим загрузки данных и искать тот, который наилучшим образом ускорит работу. Давайте реализуем такую возможность, добавив на форму "Настройки" поле "Режим загрузки данных". При этом отделим настройки почтового агента от настроек загрузки данных, вынеся их на отдельную вкладку.

В результате должна получиться форма вида:

Вкладки

В платформе нет отдельного объекта "Вкладки", вместо этого используются обычные кнопки (Button), которые изменяют видимость панелей (Panel).

Перейдем в файл TemplateSettings.xml.

Добавим цвет, которым будем подсвечивать вкладку при наведении на нее курсор мыши:

TemplateSettings.xml
<Color Name="TabMouseOverBackColor" Red="201" Green="219" Blue="241" Alpha="255" />

Создадим переменную Variable, в которой будем хранить значение активной вкладки:

TemplateSettings.xml
<MyObject Name="ActiveTabVariable" Type="Variable" Assembly="SimpleControls" ChangeForm="False">
  <Value>General</Value>
</MyObject>

Так как на форме настроек у нас будет две вкладки: основные настройки (General) и настройки почтового агента (Email), то создадим два условия:

TemplateSettings.xml
<Condition Name="GeneralTabActiveEqualCondition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="ActiveTabVariable" />
    </Item>
    <Item>General</Item>
  </Items>
</Condition>

<Condition Name="EmailTabActiveEqualCondition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="ActiveTabVariable" />
    </Item>
    <Item>Email</Item>
  </Items>
</Condition>

Создадим команду ValueSetCommand для изменения значения активной вкладки:

TemplateSettings.xml
<Command Name="ActiveTabValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Object Name="ActiveTabVariable">
    <Input />
  </Object>
</Command>

Теперь можем создать сами кнопки, которые будут выступать в качестве вкладок. Добавим описание кнопок в панель HeadPanel:

TemplateSettings.xml
<MyObject Name="EmailTabButton" Type="Button" Assembly="BaseControls">
  <Bottom>
    <Object Name="HeadPanel">
      <Property Name="Height" />
    </Object>
  </Bottom>
  <Right>
    <Formula>
      <Minus DataType="IntegerDataType">
        <Item>
          <Object Name="HeadPanel">
            <Property Name="Width" />
          </Object>
        </Item>
        <Item>10</Item>
      </Minus>
    </Formula>
  </Right>
  <Height>30</Height>
  <Width>100</Width>
  <TextAlign>MiddleCenter</TextAlign>
  <Text>Email</Text>
  <FlatStyle>Flat</FlatStyle>
  <FlatBorderSize>0</FlatBorderSize>
  <BackColor>
    <Switch>
      <Case>
        <When>
          <Condition Name="EmailTabActiveEqualCondition" />
        </When>
        <Then>ThemeBackgroundColor</Then>
      </Case>
      <Case>HeadBackgroundColor</Case>
    </Switch>
  </BackColor>
  <FlatMouseOverBackColor>
    <Switch>
      <Case>
        <When>
          <Condition Name="EmailTabActiveEqualCondition" />
        </When>
        <Then>ThemeBackgroundColor</Then>
      </Case>
      <Case>TabMouseOverBackColor</Case>
    </Switch>
  </FlatMouseOverBackColor>
  <FlatMouseDownBackColor>
    <Switch>
      <Case>
        <When>
          <Condition Name="EmailTabActiveEqualCondition" />
        </When>
        <Then>ThemeBackgroundColor</Then>
      </Case>
      <Case>TabMouseOverBackColor</Case>
    </Switch>
  </FlatMouseDownBackColor>
  <ForeColor>HeadForeColor</ForeColor>
  <FontStyle>ButtonFontStyle</FontStyle>
  <Commands>
    <Command Name="ActiveTabValueSetCommand">Email</Command>
  </Commands>
</MyObject>

<MyObject Name="GeneralTabButton" Type="Button" Assembly="BaseControls">
  <Top>
    <Object Name="EmailTabButton">
      <Property Name="Top" />
    </Object>
  </Top>
  <Right>
    <Object Name="EmailTabButton">
      <Property Name="Left" />
    </Object>
  </Right>
  <Height>30</Height>
  <Width>100</Width>
  <TextAlign>MiddleCenter</TextAlign>
  <Text>Основное</Text>
  <FlatStyle>Flat</FlatStyle>
  <FlatBorderSize>0</FlatBorderSize>
  <BackColor>
    <Switch>
      <Case>
        <When>
          <Condition Name="GeneralTabActiveEqualCondition" />
        </When>
        <Then>ThemeBackgroundColor</Then>
      </Case>
      <Case>HeadBackgroundColor</Case>
    </Switch>
  </BackColor>
  <FlatMouseOverBackColor>
    <Switch>
      <Case>
        <When>
          <Condition Name="GeneralTabActiveEqualCondition" />
        </When>
        <Then>ThemeBackgroundColor</Then>
      </Case>
      <Case>TabMouseOverBackColor</Case>
    </Switch>
  </FlatMouseOverBackColor>
  <FlatMouseDownBackColor>
    <Switch>
      <Case>
        <When>
          <Condition Name="GeneralTabActiveEqualCondition" />
        </When>
        <Then>ThemeBackgroundColor</Then>
      </Case>
      <Case>TabMouseOverBackColor</Case>
    </Switch>
  </FlatMouseDownBackColor>
  <ForeColor>HeadForeColor</ForeColor>
  <FontStyle>ButtonFontStyle</FontStyle>
  <Commands>
    <Command Name="ActiveTabValueSetCommand">General</Command>
  </Commands>
</MyObject>

Сократим ширину объекта HeadLabel, чтобы он не перекрывал кнопки.

Переименуем ContentPanel в EmailPanel и создадим GeneralPanel. Добавим в панели условия видимости с соответствующими Condition.

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

Элемент выбора режима

Перейдем в серверный xml-файл и добавим запрос:

Template.xml
<SqlQuery Name="LoadModeSelectSqlQuery">
  <Text>
    SELECT
      load_mode_id AS "LoadModeId",
      string_value(title, {PublicUserId}) AS "Title",
      string_value(description, {PublicUserId}) AS "Description",
      selected AS "Selected"
    FROM public.load_mode;
  </Text>
</SqlQuery>

В этом запросе применяется функция public.string_value(character varying, integer), которая предназначена для получения строкового ресурса из таблицы public.strings. Для этого функция использует ключ ресурса (strings_id) и системную переменную {PublicUserId} (идентификатор пользователя). По идентификатору пользователя из таблицы public.user получаем идентификатор языка (language_id).

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

Добавим новый запрос в SettingsViewSqlQueryPermission.

Скорректируем запрос SettingsUpdateSqlQuery, добавив сохранение выбранного режима загрузки данных:

Template.xml
<SqlQuery Name="SettingsUpdateSqlQuery">
  <Text>
    UPDATE template.settings
    SET
      smtp_server = {SmtpServerAddress},
      smtp_port = {SmtpServerPort},
      ssl = {SSL},
      email_address = {Email},
      email_password = {EmailPassword};
          
    UPDATE load_mode
    SET
      selected = (load_mode_id = {LoadModeId});
  </Text>
</SqlQuery>

Вернемся в файл формы настроек (TemplateSettings.xml) и создадим загружающее соединение с данными, которое будем использовать в тэге <ValueList> объекта LoadModeComboBox:

TemplateSettings.xml
<DataConnection Name="LoadModePrimaryGetDataConnection" Type="PrimaryGetDataConnection" Assembly="DataConnections">
  <SqlQuery Name="LoadModeSelectSqlQuery" Type="Select">
    <Workflow Name="Template" />
    <Fields>
      <Field Name="LoadModeId" />
      <Field Name="Title" />
      <Field Name="Description" />
      <Field Name="Selected" />
    </Fields>
  </SqlQuery>
</DataConnection>

Так же создадим вторичное соединение с данными, в котором получим запись активного режима загрузки данных и будем передавать его идентификатор в тэг <Value> объекта LoadModeComboBox:

TemplateSettings.xml
<DataConnection Name="LoadModeSelectedSecondaryGetDataConnection" Type="SecondaryGetDataConnection" Assembly="DataConnections">
  <SourceDataConnection Name="LoadModePrimaryGetDataConnection" />
  <Filter>
    <Field NativeName="Selected" />
    <Value>True</Value>
    <DataType Type="BooleanDataType" />
  </Filter>
</DataConnection>

Создайте выпадающий список "Режим загрузки данных" (LoadModeComboBox), для которого запретите ввод пустого значения (NullValue = False).

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

Для начала добавим цвет для надписи:

TemplateSettings.xml
<Color Name="HintForeColor" Red="160" Green="160" Blue="160" Alpha="255" />

А также добавим стиль шрифта:

TemplateSettings.xml
<FontStyle Name="HintFontStyle" Font="Segoe UI" Size="8" />

Добавим вторичное соединение с данными, в котором будем фильтровать записи относительно выбранного в ComboBox режима:

TemplateSettings.xml
<DataConnection Name="LoadModeDescriptionSecondaryGetDataConnection" Type="SecondaryGetDataConnection" Assembly="DataConnections">
  <SourceDataConnection Name="LoadModePrimaryGetDataConnection" />
  <Filter>
    <Field NativeName="LoadModeId" />
    <Value>
      <Object Name="LoadModeComboBox" />
    </Value>
    <DataType Type="IntegerDataType" />
  </Filter>
</DataConnection>

Создадим LoadModeDescriptionLabel:

TemplateSettings.xml
<MyObject Name="LoadModeDescriptionLabel" Type="Label" Assembly="BaseControls" ChangeForm="False">
  <Top>
    <Calculate>
      <Expression>{0} + 5</Expression>
      <Items>
        <Item>
          <Object Name="LoadModeComboBox">
            <Property Name="Bottom" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Top>
  <Left>
    <Object Name="LoadModeComboBox">
      <Property Name="Left" />
    </Object>
  </Left>
  <Height>40</Height>
  <Width>
    <Object Name="LoadModeComboBox">
      <Property Name="Width" />
    </Object>
  </Width>
  <TextAlign>TopLeft</TextAlign>
  <FontStyle>HintFontStyle</FontStyle>
  <ForeColor>HintForeColor</ForeColor>
  <Text>
    <DataConnection SourceDataConnection="LoadModeDescriptionSecondaryGetDataConnection">
      <Fields>
        <Field Name="Description" />
      </Fields>
    </DataConnection>
  </Text>
</MyObject>

Откроем форму настроек и проверим расположение элементов:

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

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

<Condition Name="LoadModeIsChangedNotEqualCondition" Type="NotEqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <DataConnection SourceDataConnection="LoadModeSelectedSecondaryGetDataConnection">
        <Fields>
          <Field Name="LoadModeId" />
        </Fields>
      </DataConnection>
    </Item>
    <Item>
      <Object Name="LoadModeComboBox" />
    </Item>
  </Items>
</Condition>

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

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

<Command Name="LoadModeChangedMessageBoxCommand" Type="MessageBoxCommand" Assembly="Commands">
  <Condition Name="LoadModeIsChangedNotEqualCondition" />
  <Caption>Изменение режима загрузки данных</Caption>
  <Text>ВНИМАНИЕ!\r\rЧтобы изменение режима загрузки данных вступили в силу, необходимо перезапустить сервер и переоткрыть приложение.</Text>
  <Icon Type="Information" />
  <Buttons Type="Ok" />
</Command>

Добавьте вызов LoadModeChangedMessageBoxCommand в команду SaveSequentialCommand сразу после вызова команды SettingsSaveCommand.

Запустите приложение и проверьте сохранение данных на форме.

Теперь можем познакомиться с утилитой WorkflowVisualizer.

Отладка приложений

Текущая версия редактора Workflow XML Editor не позволяет протестировать загрузку форм и выполнение команд. Для таких целей существует утилита WorkflowVisualizer.

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

Настройки

Утилита читает файлы логов от приложения и на их основе строит диаграммы. Чтобы приложение сохраняло логи, необходимо включить режим отладки (DebugMode) и указать каталог (DebugPath), в который будут писаться логи. Для сервера и форм режим отладки включается отдельно.

Чтобы серверная часть писала логи, необходимо в файл appsettings.json добавить поля:

"DebugMode":"true",
"DebugPath":"D:\\Template\\DebugDC"

Для включения режима на клиентской части в файл WorkflowForms.dll.config добавляются настройки:

<setting name="DebugMode" serializeAs="String">
  <value>True</value>
</setting>
<setting name="DebugPath" serializeAs="String">
  <value>D:\Template\DebugDC</value>
</setting>

Перезапустите сервер и приложение, проверьте наличие папки с файлами логов.

WorkflowVisualizer

Запустим приложение WorkflowVisualizer.

В поле DebugFolder укажем путь до нашего каталога и нажмем на кнопку "Загрузить". WorkflowVisualizer подхватит файлы логов и построит диаграмму загрузки PrimaryGetDataConnection:

Полнота диаграммы настраивается галочками:

  • SqlQuery - время обновления PrimaryGetDataConnection и конкретного SqlQuery на форме;

  • SqlQueryEngine - время исполнения запрос на контроллере сервера;

  • SqlQueryReader - время обращения сервера к базе данных;

  • PrimaryGetDataConnection - время обновления PrimaryGetDataConnection на форме. Обычно тут не бывает элементов, так как они слиты с SqlQuery;

  • SecondaryDataConnection - время обновления SecondaryDataConnection;

  • ArrayDataConnection - время обновления ArrayDataConnection;

  • ConvertDataConnection - время обновления ConvertDataConnection;

  • Condition - изменение Condition;

  • Execution - сработавшие Execution на форме;

  • Command - сработавшие команды;

  • OnSuccess - время, связанное с обновлением всех подписанных на DataConnection объектов;

  • Unknown - все остальные.

Элементы SqlQueryEngine и SqlQueryReader будут отображаться только в том случае, если в серверном файле конфигурации включен режим отладки DebugMode.

Давайте поставим галочки SqlQuery, SqlQueryEngine и SqlQueryReader и посмотрим, как изменится диаграмма:

Отлично! Теперь можем подробнее поговорить про режимы загрузки данных и увидеть, как будет меняться диаграмма при смене режима. Режимы будем рассматривать на примере карточки заказа.

Режим загрузки данных (LoadMode)

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

Режим 0 - последовательный

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

На диаграмме видно, что ClientPrimaryGetDataConnection, зависящий от поля ClientId соединения OrderPrimaryGetDataConnection, загрузился последним. А остальные загрузились в порядке описания в файле.

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

Искусственно утяжелим запрос OrderByIdSelectSqlQuery, добавив функцию pg_sleep() с задержкой на 2 секунды.

<SqlQuery Name="OrderByIdSelectSqlQuery">
  <Text>
    SELECT
      O.order_id AS "OrderId",
      O.client_id AS "ClientId",
      O.order_number AS "OrderNumber",
      O.order_date AS "OrderDate",
      O.description AS "Description",
      
      pg_sleep(2) -- "утяжеляем" запрос на 2 секунды
    FROM
      template.order O
    WHERE
      O.order_id = {OrderId};
  </Text>
</SqlQuery>

Открыв повторно карточку заказа, на диаграмме увидим картину:

На блоках OrderPrimaryGetDataConnection, OrderByIdSelectSqlQueryEngine и OrderByIdSelectSqlQueryReader пропал темный прямоугольник. Теперь блоки полностью отображают время обновления DataConnection и обработки запроса на стороне сервера и базы данных.

В такой ситуации ждать результата OrderPrimaryGetDataConnection необходимо только для ClientPrimaryGetDataConnection, так как он зависит от значения в поле ClientId. А остальные DataConnection можно запустить параллельно, чтобы не терять время в пустом ожидании.

Режим 1 - пакетный

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

На диаграмме видим, что в первый пакет попало шесть запросов. Запрос ClientShortSelectSqlQuery отправился на сервер во втором пакете после того, как выполнился самый тяжелый запрос OrderByIdSelectSqlQuery первого пакета.

Зависимостью можно управлять через тэг <DependOn>, указав в нем список DataConnection, которые должны быть выполнены раньше текущего.

Для OrderPositionPrimaryGetDataConnection и OrderPaymentPrimaryGetDataConnection укажем зависимость от OrderPrimaryGetDataConnection и посмотрим на диаграмму:

В первом пакете осталось четыре запроса, а во второй пакет к ClientShortSelectSqlQuery попали OrderPositionByOrderIdSelectSqlQuery и OrderPaymentByOrderIdSelectSqlQuery.

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

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

Режим 2 - параллельный

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

Для OrderPositionPrimaryGetDataConnection поменяем зависимость на MaterialPrimaryGetDataConnection. А с OrderPaymentPrimaryGetDataConnection удалим зависимость. Искусственно утяжелим запрос MaterialSimpleSelectSqlQuery.

В WorkflowVisualizer увидим картину:

После того как на сервер ушли все запросы первой волны, ClientPrimaryGetDataConnection не стал дожидаться завершения MaterialPrimaryGetDataConnection, а сразу же ушел на сервер, как только был готов результат OrderPrimaryGetDataConnection. В то время как OrderPositionPrimaryGetDataConnection дожидался завершения запроса, от которого зависит и отправился в третьей волне.

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

Есть разные возможности, которые позволяют разработчику контролировать порядок загрузки данных при старте формы помимо выбора режима LoadMode. С одной такой возможностью мы уже познакомились - тэг <DependOn>.

DependOn

Управление зависимостью через тэг <DependOn> в описании PrimaryGetDataConnection - так мы можем контролировать построение дерева зависимостей в момент загрузки формы.

ManualLoad

Можно перевести все PrimaryGetDataConnection в режим ручной загрузки, добавив вложенный тэг <ManualLoad> со значением True. DataConnection с ручной загрузкой не будет выполняться в момент загрузки формы. Для его обновления при старте формы необходимо вызвать команду DataConnectionRefreshCommand в Execution по условию FormLoadedCondition.

DataConnectionRefreshCommand

В команде DataConnectionRefreshCommand все DataConnection можем разбивать на пакеты, используя атрибуты Packet (разрешает загрузку DC в пакете) и PacketGroup (текстовая метка пакета):

<Command Name="AllDataConnectionRefreshCommand" Type="DataConnectionRefreshCommand" Assembly="Commands">
  <DataConnections>
    <!-- Первый пакет -->
    <DataConnection Name="1PrimaryGetDataConnection" Packet="True" PacketGroup="0"/>
    <DataConnection Name="2PrimaryGetDataConnection" Packet="True" PacketGroup="0"/>
    <!-- Второй пакет -->
    <DataConnection Name="3PrimaryGetDataConnection" Packet="True" PacketGroup="1"/>
    <DataConnection Name="4PrimaryGetDataConnection" Packet="True" PacketGroup="1"/>
  </DataConnections>
</Command>

При разбиении DataConnection на пакеты в команде типа DataConnectionRefreshCommand необходимо придерживаться следующих правил:

  • Имена пакетов, указанные в атрибуте PacketGroup, являются текстовой меткой пакета и не определяют порядок выполнения пакетов. Порядок выполнения пакетов задается порядком упоминания метки пакета в команде;

  • DataConnection из одного пакета лучше описывать друг за другом, чтобы не создавать путаницы;

  • В первый, в порядке объявления, пакет следует указывать запросы, которые не зависят от других запросов;

  • В следующий, в порядке объявления, пакет должны попадать запросы, которые зависят от запросов из предыдущего пакета;

  • Если в команде не указано явное разбиение на пакеты, то считается, что все запросы находятся в одном пакете.

В качестве эксперимента на форме карточки заказа переведем все PrimaryGetDataConnection в режим ручной загрузки (ManualLoad = True) и создадим команду, в которой все DataConnection разобьем на четыре пакета:

TemplateOrderEdit.xml
<Command Name="AllDataConnectionRefreshCommand" Type="DataConnectionRefreshCommand" Assembly="Commands">
  <DataConnections>
    <!-- Пакет 0 -->
    <DataConnection Name="SettingsPrimaryGetDataConnection" Packet="True" PacketGroup="0" />
    <DataConnection Name="AccountPrimaryGetDataConnection" Packet="True" PacketGroup="0" />
    <DataConnection Name="MaterialPrimaryGetDataConnection" Packet="True" PacketGroup="0" />

    <!-- Пакет 1 -->
    <DataConnection Name="OrderPrimaryGetDataConnection" Packet="True" PacketGroup="1" />

    <!-- Пакет 2 -->
    <DataConnection Name="OrderPositionPrimaryGetDataConnection" Packet="True" PacketGroup="2" />
    <DataConnection Name="OrderPaymentPrimaryGetDataConnection" Packet="True" PacketGroup="2" />

    <!-- Пакет 3 -->
    <DataConnection Name="ClientPrimaryGetDataConnection" Packet="True" PacketGroup="3" />
  </DataConnections>
</Command>

Вызовем эту команду в Execution, предварительно описав системное условие FormLoadedCondition:

TemplateOrderEdit.xml
<Execution>
  <ConditionExpression>
    <Condition Name="FormLoadedCondition" />
  </ConditionExpression>
  <Commands>
    <Command Name="AllDataConnectionRefreshCommand" />
  </Commands>
</Execution>

При загрузке формы увидим примерную диаграмму:

Здесь можем наблюдать повторное обновление ClientPrimaryGetDataConnection. Первое обновление произошло автоматически из-за изменения параметра ClientId, которое было вызвано обновлением OrderPrimaryGetDataConnection.

Чтобы избежать автоматического обновления данных при изменении значения параметра, следует использовать атрибут RefreshQuery со значением False.

Давайте добавим такой атрибут для параметра ClientId в ClientPrimaryGetDataConnection и проверим диаграмму загрузки:

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

Обновление объектов на форме

Но теперь возникает другая проблема на форме: ClientComboBox теряет свое значение.

Причина этого в том, что OrderPrimaryGetDataConnection обновляется раньше, чем ClientPrimaryGetDataConnection, который является источником данных для выпадающего списка. Таким образом, мы пытаемся подставить в ClientComboBox значение (Value), которого нет в списке (ValueList). И самого списка еще нет на форме. ClientComboBox забывает это значение.

Чтобы такого не происходило, используйте в ComboBox тэг <AllowOutOfList>:

<AllowOutOfList Value="True" />

Вторая особенность поведения объектов на форме при таком обновлении данных заключается в том, что у всех объектов тэг <Change> по умолчанию имеет значения:

<Change User="True" Source="True" ValueSet="True" />

И при загрузке форма будет считать, что все объекты изменились.

Поэтому указывайте тэг <Change> с нужными значениями атрибутов:

<Change User="True" Source="False" ValueSet="True" />

Параллельное выполнение запросов

По умолчанию команда DataConnectionRefreshCommand отправляет все загружающие соединения с данными в рамках одного PacketGroup на сервер одним запросом. Форма дожидается результата обновления всех запросов из пакета и после этого рассылает событие своим объектам для обновления значений.

Зачастую возникает необходимость обновлять данные на форме не дожидаясь результатов долгого запроса. Это обеспечивает плавную отрисовку формы и дает пользователю возможность начать работать на форме.

Для таких случаев команда DataConnectionRefreshCommand имеет вложенный тэг <Parallel>. Если его значение True, то все запросы в рамках одного PacketGroup отправляет на сервер параллельно. Форма не дожидается результатов всех запросов из пакета, а рассылает событие объектам по мере готовности данных.

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

И при включении параллельной отправки запросов мы получим следующую картину:

Get-проперти LoadMode

У формы есть get-проперти LoadMode, которое возвращает идентификатор выбранного режима загрузки данных. Значение этого get-проперти можно сравнивать со значением 1 - выбран ли параллельный режим загрузки:

<Condition Name="LoadModeNotEqualOneCondition" Type="NotEqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Form>
        <Property Name="LoadMode" />
      </Form>
    </Item>
    <Item>1</Item>
  </Items>
  <DataType Type="IntegerDataType" />
</Condition>

А затем использовать это условие в команде DataConnectionRefreshCommand в качестве значения тэга <Parallel>, чтобы поддерживать параллельный режим загрузки при ручном управлении загрузкой данных:

TemplateOrderEdit.xml
<Command Name="AllDataConnectionRefreshCommand" Type="DataConnectionRefreshCommand" Assembly="Commands">
  <Parallel>
    <Condition Name="LoadModeNotEqualOneCondition" />
  </Parallel>
  <DataConnections>
    <!-- Пакет 0 -->
    <DataConnection Name="SettingsPrimaryGetDataConnection" Packet="True" PacketGroup="0" />
    <DataConnection Name="AccountPrimaryGetDataConnection" Packet="True" PacketGroup="0" />
    <DataConnection Name="MaterialPrimaryGetDataConnection" Packet="True" PacketGroup="0" />

    <!-- Пакет 1 -->
    <DataConnection Name="OrderPrimaryGetDataConnection" Packet="True" PacketGroup="1" />

    <!-- Пакет 2 -->
    <DataConnection Name="OrderPositionPrimaryGetDataConnection" Packet="True" PacketGroup="2" />
    <DataConnection Name="OrderPaymentPrimaryGetDataConnection" Packet="True" PacketGroup="2" />

    <!-- Пакет 3 -->
    <DataConnection Name="ClientPrimaryGetDataConnection" Packet="True" PacketGroup="3" />
  </DataConnections>
</Command>

Итого

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

Ответы

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

Last updated