Работа с нехранимыми полями#
Правила добавления нехранимых полей#
Здесь будет рассмотрено 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)) и далее в обходчике сложить необходимые значения полей.