> For the complete documentation index, see [llms.txt](https://wfsys.gitbook.io/wt-practice/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wfsys.gitbook.io/wt-practice/multiplayer_mode/lesson_automatic-data-update.md).

# Урок 21. Автоматическое обновление данных

{% hint style="success" %}
Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье [Разворачивание проекта](https://wfsys.gitbook.io/workflow-technology/setting-up-dev-environment/manual-deployment-project).

При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела [Ответы](/wt-practice/multiplayer_mode/lesson_user_settings.md#answer) прошлого урока. Скопируйте папки *Forms*, *Workflow* и *Patterns* в папку с развернутым проектом, например, в папку *D:\WT\Projects\Template\Projects\1. Template*.

Инструкция по подключению шаблонов находится по [ссылке](/wt-practice/main/lesson_list_form.md#podklyuchenie-shablonov-k-proektu).
{% endhint %}

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

* Механизм автоматического обновления у самого PrimaryGetDataConnection, где через вложенный тэг `<UpdateInterval>` задается интервал отправки запроса на сервер;
* Использование объекта Timer, который выполняет последовательность команд с заданным интервалом. Можно контролировать время запуска таймера, задав конкретное время старта, либо использовать ручной запуск таймера. Так же можно контролировать количество выполняемых циклов до завершения работы таймера;
* Подписка формы на событие, которое рассылает сервер всем клиентским частям. В этом случае сервер отслеживает изменения данных и рассылает уведомление о событии. Клиентские части поймав уведомление проверяют, какие из открытых форм подписаны на пришедшее событие, и запускают на этих формах соответствующие обработчики.

## UpdateInterval <a href="#update_interval" id="update_interval"></a>

PrimaryGetDataConnection имеет необязательный вложенный тэг [`<UpdateInterval>`](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/primary_dc#update_interval), который включает механизм автоматического обновления данных и задает интервал обновлений.

Перейдем в файл стартовой формы и в описание OrderPrimaryGetDataConnection добавим вложенный тэг:

```xml
<UpdateInterval Seconds="20">True</UpdateInterval>
```

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

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

У этого способа есть минус: несмотря на то, что у OrderPrimaryGetDataConnection стоит тэг `<ManualLoad>` со значением True, соединение с данными все равно будет отправлять запрос на сервер. И пока пользователь не прошел аутентификацию, запрос на сервер будет подписываться пользователем WS\_GUEST, у которого недостаточно прав на выполнение запроса OrderSelectSqlQuery.

Уберем внесенные изменения.

## Timer

В WT-платформе реализован объект [Timer](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/timer), выполняющий набор команд с заданным интервалом. Этот объект можно использовать для обновления данных в таблице. Например, вместо подписки главной формы на событие, можно с помощью объекта Timer задать интервал выполнения команды OrderDataConnectionRefreshCommand.

Давайте посмотрим, как это можно реализовать.

Перейдем в файл стартовой формы и создадим объект типа Timer:

{% code title="TemplateStart.xml" %}

```xml
<MyObject Name="Timer" Type="Timer" Assembly="SimpleControls">
  <ManualStart>True</ManualStart>
  <Interval>20000</Interval>
  <Commands>
    <Command Name="OrderDataConnectionRefreshCommand" />
  </Commands>
</MyObject>
```

{% endcode %}

В тэге `<Commands>` прописали вызов команды на обновление данных о заказах. А в качестве интервала для тестов указали 20 секунды.

Обратите внимание на тэг `<ManualStart>` со значением True. Это необходимо, чтобы таймер не стартовал автоматически во время запуска формы, т.к. пользователь еще не прошел аутентификацию.

Создадим команду типа ValueSetCommand, которая будет запускать таймер, используя его set-проперти [Start](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/timer#set_start):

{% code title="TemplateStart.xml" %}

```xml
<Command Name="StartTimerValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Object Name="Timer">
    <Property Name="Start" />
  </Object>
</Command>
```

{% endcode %}

Добавим эту команду в Execution по условию LoginFormShowCommand.

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

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

Уберем внесенные изменения.

## Рассылка событий <a href="#dispatch-events" id="dispatch-events"></a>

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

### Генерация события <a href="#event-generation" id="event-generation"></a>

Для тэга [`<SqlQuery>`](https://wfsys.gitbook.io/workflow-engine-syntax/workflow_engine/sql_queries/sql_query) существует вложенный тэг [`<Events>`](https://wfsys.gitbook.io/workflow-engine-syntax/workflow_engine/sql_queries/sql_query#events), содержащий список событий, которые будут переданы на все клиентские приложения после выполнения запроса.

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

Каждое событие, описываемое тэгом `<Event>`, содержит набор параметров, передаваемых на форму, подписанную на это событие.

Давайте для запроса **OrderUpdateSqlQuery** добавим событие **ChangedNumberOfOrders**:

{% code title="Template.xml" %}

```xml
<SqlQuery Name="OrderUpdateSqlQuery">
  <Events>
    <Event Name="ChangedNumberOfOrders">
      <Parameters>
        <Parameter Name="NumberOfOrders">
          SELECT
            count(*)
          FROM
            template.order O
          WHERE
            O.added AND
            NOT O.deleted;
        </Parameter>
      </Parameters>
    </Event>
  </Events>
  <Text>
    UPDATE template.order
    SET
      order_number = {OrderNumber},
      order_date = {OrderDate},
      client_id = {ClientId},
      description = {Description},
      added = true
    WHERE
      order_id = {OrderId};
  </Text>
</SqlQuery>
```

{% endcode %}

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

### Подписание формы на событие <a href="#signing-form-for-event" id="signing-form-for-event"></a>

Чтобы подписать форму на какое-либо событие, необходимо создать объект типа [Subscriber](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/subscriber), в котором указать имя события или имена нескольких событий.

Давайте перейдем в файл стартовой формы (TemplateStart.xml) и подпишем ее на добавленное ранее событие:

{% code title="TemplateStart.xml" %}

```xml
<MyObject Name="NumberOfOrdersSubscriber" Type="Subscriber" Assembly="SimpleControls">
  <Subscribes>
    <Subscribe>ChangedNumberOfOrders</Subscribe>
  </Subscribes>
</MyObject>
```

{% endcode %}

Теперь необходимо проверять, что событие сработало. Для этого воспользуемся get-проперти [Subscribe](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/subscriber#get_subscribe), которое возвращает название события последнего принятого сообщения:

{% code title="TemplateStart.xml" %}

```xml
<Condition Name="NumberOfOrdersSubscriberChangedCondition" Type="ChangedCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="NumberOfOrdersSubscriber">
        <Property Name="Subscribe" />
      </Object>
    </Item>
  </Items>
</Condition>
```

{% endcode %}

Так как у нас только одно событие, на которое подписана главная форма, то достаточно использовать условие [ChangedCondition](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/conditions/event_condition/changed_condition).

Если бы главная форма была подписана на несколько событий, то мы бы использовали [EqualCondition](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/conditions/comparison_condition/equal_condition), чтобы сопоставлять значение get-проперти с именами событий:

```xml
<Condition Name="FirstEventNameSubscriberEqualCondition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="Subscriber">
        <Property Name="Subscribe" />
      </Object>
    </Item>
    <Item>FirstEventName</Item>
  </Items>
</Condition>

<Condition Name="SecondEventNameSubscriberEqualCondition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="Subscriber">
        <Property Name="Subscribe" />
      </Object>
    </Item>
    <Item>SecondEventName</Item>
  </Items>
</Condition>
```

А еще можно использовать get-проперти [Parameter](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/subscriber#get_parameter) - получать значение конкретного параметра и применять его в условии.

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

{% code title="TemplateStart.xml" %}

```xml
<Execution>
  <ConditionExpression>
    <Condition Name="NumberOfOrdersSubscriberChangedCondition" />
  </ConditionExpression>
  <Commands>
    <Command Name="OrderDataConnectionRefreshCommand" />
  </Commands>
</Execution>
```

{% endcode %}

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

Так как теперь стартовая форма получает сообщения от сервера о новых данных и автоматически обновляет OrderPrimaryGetDataConnection, то из `<Execution>` при добавлении или изменении заказа нужно убрать вызовы команды OrderDataConnectionRefreshCommand, чтобы не было повторного обращения к серверу. Но оставить команду на выделение измененной строки в таблице.

## Итоги <a href="#results" id="results"></a>

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

Тэг `<UpdateInterval>` у PrimaryGetDataConnection позволяет автоматически обновлять данные, и не требует создания дополнительных команд, условий и Execution. Но нужно следить за правами доступа. И нельзя контролировать время запуска и количество циклов выполнения обновления.

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

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

## Ответы <a href="#answer" id="answer"></a>

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

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th data-hidden data-type="content-ref"></th></tr></thead><tbody><tr><td>lesson21-answer.zip</td><td><a href="https://wfsys.ru/download/wt_practice_desktop_answers/lesson21-answer.zip">https://wfsys.ru/download/wt_practice_desktop_answers/lesson21-answer.zip</a></td></tr></tbody></table>
