# Работа с объектами и коллекциями

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

## Бизнес-объект

Бизнес-объект (БО) — это объединение нескольких классов и их коллекций в группу для работы с ними в кеше и конфигурировании вспомогательных сервисов. 

Бизнес-объект позволяет:

- массово загружать данные в транзакционный кеш.  
  Для бизнес-объекта можно указать стратегию загрузки данных,
  существенно уменьшающую количество запросов в базу данных, так как запросы выполняются не для каждого объекта, а
  для каждого класса бизнес-объекта.
- настраивать права доступа.  
  Для бизнес-объекта создаётся административный объект, на котором
  можно массово выдать привилегии для всех классов бизнес-объекта.
- управлять электронной подписью.  
  Можно настроить правила подписи всего бизнес-объекта, включая 
  не только шапку, но и все вложенные коллекции.
- настраивать интеграцию и репликацию.

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

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

Примеры навигации:

```scala
val empApi = EmployeeApi()
empApi.load(7452) :/ { id =>
  println(id.id)
  for(idDes <- AddressApi.byParent(id)){
    println(s"address: city=${ idDes.get(_.city)}")
  }
} 
```

## Коллекции

Коллекция — это сущность, объекты которой не могут существовать без
ссылки на объект-владелец. Классы коллекций объявляются в ODM
сущности-владельца.

В бизнес-объекте предполагается использование ленивых коллекций.
Элементы коллекции загружаются по необходимости. Удаление и добавление
происходит в фоновых коллекциях и не требует немедленного запроса к базе
данных.

Связывание сущностей владельца и коллекции
производится путём объявления элемента `collection` в секции
`collections`:

```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<class xmlns="http://www.global-system.ru/xsd/global3-class-1.0"
      name="Bs_Brigade" caption="Бригада"
      cardEditor.representation="Card"
      listEditor.representation="List"
      viewOptions.openCardType="mdi" 
      supertype="reference">
  <attributes>
    <attr name="id" 
          attribute-type="Long"
          caption="Идентификатор" 
          order="-1" 
          type="basic"
          isVisible="false"/>
    <attr name="idClass" 
          attribute-type="Long"
          caption="idClass" 
          order="-2" 
          type="basic"
          isVisible="false"/>
    <attr name="gid" 
          attribute-type="Varchar"
          isVisible="false"/>
    <attr name="sCode" 
          attribute-type="Varchar" 
          caption="Код"
          order="10" 
          isMnemoCode="true" 
          type="basic"
          isVisible="true">
      <autonum id="1">
        <dimension name="Dim1"/>
      </autonum>
    </attr>
    <attr name="sCaption" 
          attribute-type="Varchar"
          caption="Наименование" 
          order="20" 
          isHeadLine="true"
          type="basic"
          isVisible="true"/>
    <attr name="idDepartment" 
          attribute-type="Long"
          caption="Подразделение" 
          order="30" 
          type="refObject"
          ref.class="Bs_Department"/>
    <attr name="idForeman" 
          attribute-type="Long"
          caption="Бригадир" 
          order="40" 
          type="refObject"
          ref.class="Bs_Employee"/>
    <attr name="idMaster"   
          attribute-type="Long"
          caption="Мастер" 
          order="50" 
          type="refObject"
          ref.class="Bs_Employee"/>
  </attributes>
  <collections>
    <collection name="Bs_BrigadeStaff" 
                ref.attr="idBrigade"
                cascadeOnDelete="true"/>
  </collections>
  <dbData>
    <script name="dataInstall" version="1">
      <install>Bs_BrigadeApi.dataInstall()</install>
    </script>
  </dbData>
</class>
```

Для класса коллекции указывается супертип `collection`. Это гарантирует
наследование Scala-классов от необходимых системных классов.

```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<class xmlns="http://www.global-system.ru/xsd/global3-class-1.0"
       name="Bs_BrigadeStaff"
       caption="Состав бригады"
       cardEditor.representation="Card"
       listEditor.representation="List"
       viewOptions.openCardType="mdi"
       supertype="collection">
  <attributes>
    <attr name="id"
          attribute-type="Long"
          caption="Идентификатор"
          order="-1"
          type="basic"
          isVisible="false"/>
    <attr name="idClass"
          attribute-type="Long"
          caption="idClass"
          order="-2"
          type="basic"
          isVisible="false"/>
    <attr name="gid"
          attribute-type="Varchar"
          isVisible="false"/>
    <attr name="idEmployee"
          attribute-type="Long"
          caption="Сотрудник"
          order="10"
          type="refObject"
          ref.class="Bs_Employee"
          isMnemoCode="true"/>
    <attr name="idBrigade"
          attribute-type="Long"
          caption="Бригада"
          order="20"
          type="refObject"
          ref.class="Bs_Brigade"
          genListCollectionRep="true"/>
    <attr name="dStart"
          attribute-type="Date"
          caption="Дата начала"
          order="30"
          type="basic"
          editorType="datePick"/>
    <attr name="dEnd"
          attribute-type="Date"
          caption="Дата окончания"
          order="40"
          type="basic"
          editorType="datePick"/>
    <attr name="bisForeman"
          attribute-type="Number"
          caption="Является бригадиром"
          order="50"
          type="basic"
          editorType="check"/>
  </attributes>
</class>
```

### Формирование кода

При формировании кода сущности-владельца пересоздаётся код
всех коллекций. `Dpi` коллекции наследуется от `ChildApi`:

```scala
trait Xxx_XxxxDpi[T] extends ChildApi[java.lang.Long, ARO, API]
```

`Dvi` наследуется от `CollectionAvi`:

```scala
trait Xxx_XxxxDvi extends CollectionAvi
```

Основные методы для работы:

```scala
//создание по владельцу, возвращает rop созданного объекта
Api().insertByParent(ropMaster)
//загрузка всей коллекции по владельцу, возвращает обходчик записей отфильтрованных по rop предка
Api().byParent(ropMaster) 
Api().byParent(idMaster) 
//удаление по rop объекта
Api().delete(rop)
```

### Отображения-детали

Для формирования отображений выборки, данные которых ограничены по
значению ссылочного поля, необходимо в ODM-файле для соответствующего
ссылочного атрибута указать свойство `genListCollectionRep="true"`. Будет
сформировано отображение `List_{attr}`.

```xml
<attr name="idBrigade"
      attribute-type="Long"
      caption="Бригада"
      order="20"
      type="refObject"
      ref.class="Bs_Brigade"
      genListCollectionRep="true"/>
```

### Переменная ссылочность

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

```{note}
У классов, на объекты которых может ссылаться коллекция
с переменной ссылочностью, обязательно должен существовать атрибут `gid`.
Рекомендуется индексировать это поле.
```

### Преобразование коллекции в самостоятельный тип

Если объекты коллекции должны существовать независимо от объекта-родителя, использоваться несколькими объектами-родителями или иметь собственный жизненный цикл, класс с супертипом `collection` или `vcollection` может быть преобразован в самостоятельный тип, например `reference`, `journal` или `document`.

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

#### Изменение модели

При преобразовании коллекции необходимо:

1. Изменить супертип класса с `collection` или `vcollection` на требуемый самостоятельный тип.
2. Удалить объявление коллекции из Odm-файла класса объекта-родителя.
3. Удалить из преобразованного класса ссылку на объект-родитель, если прямая связь с ним больше не используется.
4. Если связь между объектами должна сохраниться в другом виде, создать отдельный класс связи. Например, для отношения many-to-many класс связи содержит ссылки на преобразованный объект и объект-родитель.
5. Обновить ORM-описания, API, выборки, пользовательские интерфейсы и другую прикладную бизнес-логику, использующую прежнюю структуру данных.

После преобразования объект перестает быть элементом коллекции и становится самостоятельной сущностью. Дальнейшая работа с ним определяется супертипом, выбранным при преобразовании.

#### Миграция данных

Необходимость миграции и ее состав зависят от того, как после преобразования должна измениться связь с объектом-родителем.

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

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

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

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

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

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

#### Пересчет структуры бизнес-объекта

Если при преобразовании изменяется положение класса в структуре бизнес-объекта, необходимо пересчитать значение `gidRoot_dz`.

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

Например:

```scala
Btk_ClassUtilsPkg.recalcGidRoot("Vci_GitProject")
```

При изменении положения класса ранее рассчитанный путь внутри бизнес-объекта автоматически не обновляется. Без пересчета аудит может продолжить записывать изменения в журнал прежнего корневого объекта. Некорректно могут работать и другие механизмы, использующие `gidRoot_dz` для определения корня или пути объекта внутри бизнес-объекта.

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

Подробнее о разработке релизов, обычных и отложенных миграционных скриптах см. в разделе [Релизы](https://help.globalerp.ru/books/GlobalServerAppGuide/SNAPSHOT/html/070_development/030_%D1%80%D0%B5%D0%BB%D0%B8%D0%B7%D1%8B.html).

### Преобразование коллекции в переменную коллекцию

Если одна коллекция должна использоваться для объектов-родителей разных классов, класс с супертипом `collection` можно преобразовать в переменную коллекцию (`vcollection`).

При преобразовании к прямой ссылке на объект-родитель добавляется переменная ссылка, в которой хранится GID объекта-родителя.

#### Изменение модели

При преобразовании коллекции необходимо:

1. Изменить супертип класса с `collection` на `vcollection`.
2. Добавить атрибут переменной ссылочности для хранения GID объекта-родителя.

Например, при преобразовании класса `Btk_AccessPeriodByUser` в `vcollection` был добавлен атрибут `gidObj`:

```xml
<attr name="gidObj"
      attribute-type="Varchar"
      caption="Источник"
      order="30"
      type="refAnyObject"
      isRequired="true"
      genListCollectionRep="true"/>
```

У классов, на объекты которых может ссылаться переменная коллекция, должен существовать атрибут `gid`.

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

Если в коллекции уже существуют записи, их связь с объектом-родителем необходимо перенести на новую переменную ссылку. Например, при преобразовании `Btk_AccessPeriodByUser` существующие связи через `idAccessByPeriod` были перенесены на `gidObj` миграционным скриптом.

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

### Каскадное удаление

Свойство «Каскадное удаление» у коллекции включено по умолчанию. Если оно включено:

```xml
<collections>
  <collection cascadeOnDelete="true"
              name="Bs_PersonIdentityDoc"
              ref.attr="idPerson"/>
  <collection cascadeOnDelete="true"
              name="Bs_PersonProf"
              ref.attr="idPerson"/>
</collections>
```

В `Dpi` мастера формируется вызов метода `delete` для коллекций.

Для подключённых `vcollection` (переменных коллекций) код удаления
необходимо писать вручную в `Api`:

```scala
override def delete(rop: ApiRop): Unit = {
  for (crop <- Bs_BankAccApi().byParent(rop)) {
    Bs_BankAccApi().delete(crop)
  }
  for (crop <- Bs_DefSettlerAddressApi().byParent(rop)) {
    Bs_DefSettlerAddressApi().delete(crop)
  }
  for (crop <- Bs_SettlerAddressApi().byParent(rop)) {
    Bs_SettlerAddressApi().delete(crop)
  }
  super.delete(rop)
}
```

## Навигация в рабочем пространстве

Объекты загружаются из базы данных или из кросс-сессионного кеша. При
загрузке происходит пессимистическая либо оптимистическая блокировка.
Объекты находятся в рабочем пространстве до момента коммита. В момент
коммита рабочее пространство очищается.

При навигации можно модифицировать объекты. `Api` гарантирует
корректную навигацию по мастер-деталям без необходимости выполнять `flush` и `clean`
кеша или немедленно загружать коллекции.

## Массовая загрузка объектов

При массовой загрузке объектов сокращается количество обращений к базе
данных. При обходе в обычном режиме трёхуровневого
бизнес-объекта выполняется `n + 2` запроса, где `n` — количество деталей второго
уровня: один запрос выполняется для мастер-объекта, а два — для коллекций
второго уровня. Если объект запросить с помощью массового запроса,
при его обходе выполняется всего три запроса. Это может ускорить
навигацию по объектам более чем в 10 раз.

Пример массового запроса:

```scala
for (rv <- new OQuery(Stk_WarrantAta.Type){
  where (t.id in idap)
  batchAll()
}){}
```

```{note}
Объектные запросы активно расходуют оперативную память. Это накладывает ограничение на использование их в процедурах бизнес-логики. Обычно объектные запросы используются для организации пользовательского интерфейса (редактирование одного объекта с коллекциями, справочники, документы и т.д.), а для программирования внутренних процедур бизнес-логики используются SQL-запросы в базу данных.
```

## Работа с провайдерами строк

Провайдер строки `Rop` используется для работы со строкой данных (`Aro`),
загруженной в рабочее пространство, и гарантирует, что при
доступе к строке данная строка будет находиться в рабочем пространстве.

Метод получения `Rop`:

```scala
thisApi().load(идентификатор.asNLong)
```

Примеры сеттеров в файле выборки:

```scala
val rop = thisRop
thisApi().setidContras(rop,getVar("super$id").asNLong)
```

Работа с `Rop` в API:

```scala
for (ropGrade <- new OQuery(entityAta.Type){  
  where (t.idGdsGrade === idpGdsGrade)  
}) {  
  setidGdsGrade(ropGrade, None.nl)  
}
```

Работа с `AnyRop` (`Rop` неизвестного типа):

```scala
anyRop match {
  case Btk_GroupApi(ropGroup) =>
    ropGroup.get(_.sCaption)
  case Btk_ClassApi(ropClass) =>
    ropClass.get(_.sName)
  case _ => throw AppException("Ожидали роп группы или класса")
}

// получить из списка только ропы определенного класса
ropaAny
  .collect {
    case Btk_GroupApi(ropGroup) => ropGroup
  }
```

## Оптимистическая блокировка

Так как система Global Postgres ориентирована на работу с короткими
транзакциями, фреймворк по умолчанию включает для классов
оптимистическую блокировку.

Принцип работы оптимистической блокировки:

- При загрузке строки в кеш запоминается версия изменения.
- Если строка изменяется, версия изменения увеличивается.
- При сохранении в базу данных проверяется, соответствует ли старая версия
  изменения версии изменения в базе данных. Если версии не совпадают,
  выдаётся ошибка применения изменений.

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

```xml
<class useOptimisticLocking="false"/>
```

```{note}
Для хранения версии изменений используется служебное
поле `nVersion_dz`.
```