Работа с нехранимыми полями#

Правила добавления нехранимых полей#

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

Для формирования нехранимого поля нужно:

  • Получить поле в запросе под заданным псевдонимом.

  • Описать поле в разметке avm под тем же псевдонимом.

Выбор подхода реляционный или объектный запрос#

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

Например: в карточке документа нужно посчитать сумму по всем записям в коллекции. Если поле суммы реализовать реляционно, то оно будет формироваться только из согласованных в БД значений. Это значит, для того чтобы учитывались новые записи в коллекции, их нужно закоммитить (сохранить) в БД. Если же поле реализовать объектно, то в нём будут учитываться данные из кэша, т.е. те самые созданные новые записи в коллекции, которые ещё не закоммичены в БД.

Реализация через реляционный запрос#

Примечание

Не учитывает значения из кэша, которые не закоммичены в БД.

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

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

Dvi:

    override protected def selectStatement: String = {
  s"""SELECT
    t.id  
    ,t.idClass  
    ,t.idObjectType  
    ,t1.sHeadLine_dz as idObjectTypeHL   --нехранимое поле
    ,t.dReg  
    ,t.sRegNum  
    ,t.idState  
    ,t2.sHeadLine_dz as idStateHL    --нехранимое поле 
    FROM Rzd_Train t
      LEFT JOIN  Btk_ObjectType t1 on t.idObjectType = t1.id
      LEFT JOIN  Btk_ClassState t2 on t.idState = t2.id
    """
}

dvm:

<representation name="Default">
    <attributes>
        <attr name="idObjectType" caption="Тип документа" isVisible="false" editorType="edit" order="10" isRequired="true"/>
        <attr name="idObjectTypeHL" caption="Тип документа" order="10.2" isRequired="true">
            <editor>
                <buttonsEdit canEditText="true" changeableAttr="idObjectType">
                    <buttons lookup="true" openCard="true" reset="true"/>
                </buttonsEdit>
            </editor>
            <ref class="Btk_ObjectType"/>
        </attr>
        <attr name="idState" caption="Состояние" isVisible="false" editorType="edit" order="40" isRequired="true"/>
        <attr name="idStateHL" caption="Состояние" order="40.2" isRequired="true">
            <editor>
                <lookup changeableAttr="idState" isLookupLazyLoad="true" lookupKeyAttr="id" lookupListAttr="sHeadLine" lookupQuery="gtk-ru.bitec.app.btk.Btk_ClassStateAvi#Lookup_Class"/>
            </editor>
            <ref class="Btk_ClassState"/>
            <grid columnWidth="12"/>
        </attr>
    </attributes>
</representation>

Примечание

Напомню, что Dvi и dvm - это результат работы кодогенератора по данным описанным в разметке odm. Разработчик работает в соответствующих файлах Avi и avm, переопределяя содержимое Dvi и dvm.

ВНИМАНИЕ! Изменять Dvi и dvm нельзя.

А точнее бесполезно, потому что изменения будут перетёрты при следующем запуске кодогенератора.

Реализация через объектный запрос#

Примечание

Учитывает значения из кэша, которые не закоммичены в БД.

Вычисляемые поля через SQL-запрос в onRefreshExt#

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

С помощью onRefreshExt в отображении Card получены хэдлайны в Dvi:

    override protected def onRefreshExt: String = {
      s"""with t as ( select 
       :id as id
      ,:idObjectType as idObjectType
      ,:idState as idState
      ,:gidSrc/*@NString*/ as gidSrc
    )
    SELECT
       t.id
      ,t1.sHeadLine_dz as idObjectTypeHL
      ,t2.sHeadLine_dz as idStateHL
      ,t3.sHeadLine as gidSrcHL
    FROM t
      LEFT JOIN  Btk_ObjectType t1 on t.idObjectType = t1.id
      LEFT JOIN  Btk_ClassState t2 on t.idState = t2.id
      LEFT JOIN  Btk_Object t3 on t.gidSrc = t3.gidRef
    """
    }

Здесь используется синтаксис postgresql.

Применяется конструкция with (ссылка).

Через ":" подставляются текущие значения атрибутов с выборки (в том числе из кэша). Так, например, ":id as id" означает, что с выборки будет получено текущее значение атрибута id и подставлено в таблицу t под псевдонимом поля id. Теперь обращение в основном запросе t.id будет возвращать текущее значеине id.

Примечание

Под фразой «текущее значение» понимается значение на момент обновления выборки и выполнения метода onRefreshExt.

Примечание

Описание в dvm не меняется в зависимости от метода получения значения нехранимого поля (реляционного или объектного) и останется таким же, как в случае формирования нехранимого поля в selectStatement.

Вычисляемые поля через case class AdditionalInfo#

Нехранимое поле можно сформировать, переопределив onRefresh и onRefreshItem.

В этом случае объект представляют как кортеж (SRop, экземпляр case class’а). Тем самым объект имеет поля записи, описанных в БД (хранимые поля), и поля case class’а (нехранимые).

В этом случае принято называть case class AdditionalInfo, а заполнение описывать в методе getAdditionalInfo.

Нехранимое поле в отображении Card:

case class AdditionalInfo(
                           var nQtyLoadRecievActs: NNumber,
                           var nQtyLoadTransferCertificate: NNumber,
                           var nQtyLoadTransferCertificateAccounted: NNumber,
                           var nQtyRemains: NNumber,
                           var nQtyReserv: NNumber
                         )

protected def getAdditionalInfo(rop: Oil_ExternalMovApi#ApiRop): AdditionalInfo = {
  AdditionalInfo(
    nQtyLoadRecievActs = None.nn
    , nQtyLoadTransferCertificate = None.nn
    , nQtyLoadTransferCertificateAccounted = None.nn
    , nQtyRemains = None.nn
    , nQtyReserv = None.nn
  )
}

override protected def onRefresh: Recs = {
  val rop = thisApi().load(getVar(CardRep.IdItemSharp).asNLong)
  (rop, getAdditionalInfo(rop))
}

override protected def onRefreshItem: Recs = {
  val rop = thisApi().load(getVar(getPKFieldName).asNLong)
  (rop, getAdditionalInfo(rop))
}

Примечание

Переменные внутри case class необходимо указывать var для Avi

Нехранимое поле в отображении List_idBrigade коллекции:

trait List_idBrigade extends Default with super.List_idBrigade {

  case class AdditionalInfo(var sEmpId: NString = None.ns,
                            var sPosition: NString = None.ns)

  def getAdditionalInfo(rop: Bs_BrigadeStaffApi#ApiRop): AdditionalInfo = {
    if (rop.get(_.idEmployee).isNotNull) {
      val avEmploee = Bs_EmployeeApi().load(rop.get(_.idEmployee)).copyAro()
      AdditionalInfo(
        sEmpId = avEmploee.sEmpId,
        sPosition = avEmploee.sPosition)
    } else AdditionalInfo()
  }

  override protected def onRefresh: Recs = {
    Bs_BrigadeStaffApi().refreshByParent(getVarWithDep("super$id").asNLong)
      .map(rop => {
        (rop, getAdditionalInfo(rop))
      })
  }
}

Такой способ самый универсальный.

Например, с помощью onRefreshExt не получится посчитать сумму по всем записям коллекции с учётом кэша (для этого необходимо присоединить таблицу коллекции по условию idparent = t.id, что обеспечит учёт только тех записей и их значений, которые закоммичены в БД, т.е. без учёта кэша).

В рассматриваемой реализации, можно получить все записи коллекции с учётом кэша с помощью метода refreshByParent(rop.get(_.id)) и далее в обходчике сложить необходимые значения полей.