Введение
Вычислимые свойства - это специальные атрибуты объекта данных, значения которых вычисляются динамически на основе других свойств. Вычислимые свойства объекта не сохраняются в базе данных и их значение вычисляется через getter свойства или СУБД (если указан DataServiceExpression - специфическое SQL-выражение).
Создание вычислимого свойства
Вычислимые свойства могут быть сгенерированы через Flexberry Designer. Для создания вычислимого свойства необходимо:
-
Сделать свойство объекта нехранимым на уровне UML-модели.
-
Указать
DataServiceExpressionдля свойства

Для этого, необходимо нажать на карандаш, и в выпадающем окошке заполнить необходимое количество DataServiceExpressions:

О том, как заполнять DSE, см. в разделе Правила заполнения вычислимого свойства.
Каждый DataServiceExpression определяет выражение для определённого сервиса данных (напр. ICSSoft.STORMNET.Business.PostgresDataService). Допускается использование ICSSoft.STORMNET.Business.SQLDataService (выражение будет работать для всех SQL-подобных сервисов данных).
В результате, после генерации вычислимое свойство будет:
- Помечено атрибутом
[NotStored] - Иметь логику вычисления в атрибутах
[DataServiceExpression](SQL) - каждыйDSEсоответствует своей СУБД - Иметь пустой аксессор
getобъекта данных (C#)
Для полноценной работы свойства необходимо вручную заполнить:
- Логику аксессора
getобъекта данных (C#) (и дляset- в случае необходимости) computedсвойство (в приложенияхember-flexberry- см. Вычислимые свойства и проекции моделей)
Пример вычислимого свойства
[DataServiceExpression(typeof(SQLDataService),"'\"'+ @Наименование@+'\"'")]
[NotStored]
public string НаименованиеВКавычках
{
get
{
return "\"" + Наименование + "\"";
}
}
Правила заполнения вычислимого свойства
DataServiceExpression(DSE)
- атрибуты объекта в выражении
DSEдолжны быть выделены символом @ (собака):@Наименование@ - указывать
DSEнеобязательно, но это будет влиять на производительность (см. раздел) в некоторых случаях при отсутствииDSEполе может быть не вычислено - при определении
DataServiceExpressionнеобходимо указать тип сервиса данных, который поддерживает данное выражение:DataServiceExpression(typeof(SQLDataService), ...)
Для чего используется DataServiceExpression
В некоторых случаях при загрузке объекта (при
LoadObjectStringedView), сервисы данных могут загружать данные в виде строк, не создавая при этом объектов данных. В таком случае для вычисления нехранимых свойств используется СУБД - применяется SQL-выражение изDataServiceExpression. ЕслиDataServiceExpressionне указан, сервису данных приходится создавать объект данных в C# (DataObject), чтобы получить значение вычислимых свойств через аксессорыget. Поэтому, для экономии ресурсов сервера, заполнениеDataServiceExpressionпозволит вычислять нехранимые поля средствами СУБД (это гораздо экономнее, чем создаватьDataObjectдля расчёта свойств).
DataServiceExpression- Аксессор
get.
- Аксессор должен быть задан у каждого вычислимого свойства.
public string ФИО
{
get => $"{Фамилия} {Имя} {Отчество}";
}
Если для загрузки объекта используется только метод
LoadObjectStringedViewи у свойства заданDataServiceExpression, аксессор задавать необязательно.
computedсвойство (для приложенийember-flexberry). Данное свойство задавать необязательно, но желательно - т.к. это позволит обновлять поле сразу же, не дожидаясь загрузки обновлённого значения с сервера - см. подробнее
Загрузка вычислимых полей
В представлении, по которому происходит загрузка, должны присутствовать все свойства, от которых зависит вычислимый атрибут. Несоблюдение этого приведёт к неверному расчёту значения свойства.
Примеры
- Пример 1. Вычислимое свойство по детейлам
- Пример 2. Первичные ключи в DataServiceExpression
- Пример 3. Применение вычислимых полей в вычислимых полях
Пример 1. Вычислимое свойство по детейлам
Будет разобран процесс создания вычислимого свойства по значениям детейлов на примере системы учета покупателей. В этой системе фиксируются покупки клиентов, каждая из которых может иметь статус Передано в банк или Оплачено. Необходимо добавить к покупателю поле Сумма оплаченных покупок, которое будет вычисляться автоматически.
Шаг 1: Моделирование в Flexberry Designer
В первую очередь, следует создать через Flexberry Designer диаграмму классов.

Поле СуммаОплаченныхПокупок класса Покупатель должно быть нехранимым. Затем в атрибуте
DataServiceExpression этого поля необходимо добавить SQL-выражение через Flexberry Designer.
"SELECT SUM(purchase."Сумма")
FROM "Покупатель" customer join "Покупка" purchase on customer."primaryKey" = purchase."Покупатель"
WHERE purchase."Покупатель" = StormMainObjectKey AND purchase."Статус" = 'Оплачено' "
В поле
DataServiceво Flexberry Designer необходимо указатьICSSoft.STORMNET.Business.SQLDataService, т.к. в выражении нет специфичных для Postgres или MSSQL функций.
Стоит отметить, что в ограничениях по значению перечисления, необходимо указывать заголовок значения. Например, в C# имеется значение
"ОплаченоПокупателем", и заголовок"Оплачено покупателем". В ограничении необходимо использовать именно заголовок"Оплачено покупателем".
Шаг 2: Генерация программного кода
В результате генерации будет создан следующий код:
[ICSSoft.STORMNET.NotStored()]
[DataServiceExpression(typeof(ICSSoft.STORMNET.Business.SQLDataService), "SELECT SUM(Purchase.\"Сумма\")"+
" FROM \"Покупатель\" customer join \"Покупка\" Purchase on customer.\"primaryKey\" = Purchase.\"Покупатель\""+
" WHERE Purchase.\"Покупатель\" = StormMainObjectKey AND Purchase.\"Статус\" = \'Оплачено\' ")]
public virtual decimal СуммаОплаченныхПокупок
{
get { throw new NotImplementedException(); } // логика вычисления в get должна быть реализована самостоятельно, иначе свойство не будет возвращать актуального значения в C# коде
set {}
}
В результате, поле СуммаОплаченныхПокупок будет вычисляться без необходимости создавать объект данных для каждого покупателя.
Шаг 3: Оптимизация работы с кэшированным значением
Для улучшения производительности и сохранения данных при редактировании объекта Покупатель рекомендуется реализовать кэширование значения вычислимого поля. Для этого в классе Покупатель следует создать приватные переменные для хранения кэшированных сумм.
public class Покупатель : ICSSoft.STORMNET.DataObject
{
private ICSSoft.STORMNET.UserDataTypes.NullableDecimal cashedPurchaseSum = null; // переменная кэша для поля СуммаОплаченныхПокупок
private ICSSoft.STORMNET.UserDataTypes.NullableDecimal cashedAvailableSum = null; //переменная кэша для поля ДоступнаяСумма
// ...
}
Затем необходимо скорректировать аксессоры свойства СуммаОплаченныхПокупок для использования кэшированного значения.
[ICSSoft.STORMNET.NotStored()]
[DataServiceExpression(typeof(ICSSoft.STORMNET.Business.SQLDataService), "SELECT SUM(purchase.\"Сумма\")"+
" FROM \"Покупатель\" customer join \"Покупка\" purchase on customer.\"primaryKey\" = purchase.\"Покупатель\""+
" WHERE purchase.\"Покупатель\" = StormMainObjectKey AND purchase.\"Статус\" = \'Оплачено\' ")]
public virtual decimal СуммаОплаченныхПокупок
{
get => this.cashedPurchaseSum;
set
{
if (value != null)
{
this.cashedPurchaseSum = value;
}
}
}
Шаг 4: Дополнительные вычисляемые свойства
Пусть условие задачи будет расширено. Необходимо добавить поле Доступная сумма, которое показывает доступные средства на счету клиента после оплаты.
Код свойства будет иметь следующий вид:
// *** Start programmer edit section *** (Покупатель.ДоступнаяСумма CustomAttributes)
[DataServiceExpression(typeof(SQLDataService), "SELECT @СуммаНаСчёте@ - SUM(purchase.\"Сумма\") "+
" FROM \"Покупатель\" customer join \"Покупка\" purchase on customer.\"primaryKey\" = purchase.\"Покупатель\" "+
" WHERE purchase.\"Покупатель\" = StormMainObjectKey AND purchase.\"Статус\" = \'Передано в банк\' ")]
// *** End programmer edit section *** (Покупатель.ДоступнаяСумма CustomAttributes)
[ICSSoft.STORMNET.NotStored()]
public virtual decimal ДоступнаяСумма
{
get => this.cashedAvailableSum;
set
{
if (value != null)
{
this.cashedAvailableSum = value;
}
}
}
Пример 2. Первичные ключи в DataServiceExpression
Использовать первичные ключи в вычислимых полях можно посредством указания STORMMainObjectKey без символа “@”.
Например, выражение DSE для сообщения вида “Улица <Название> имеет первичный ключ <Первичный_Ключ_Улицы>" будет выглядеть следующим образом.Первичный_Ключ_Улицы>Название>
[DataServiceExpression(typeof(SQLDataService), "\'Улица \' + @Название@ + \' имеет первичный ключ \' +CAST(STORMMainObjectKey as varchar(max))")]
//...
При использовании данного свойства как мастерового в sql-запросе STORMMainObjectKey будет корректно заменён на STORMJoinedMasterKey:
('Улица ' + "IIS.TestStandWinforms.Дом"."Улица.Название" + ' имеет первичный ключ ' + CAST("STORMJoinedMasterKey0" as varchar(max))) as "Улица.NotStoredName"
Пример 3. Использование вычислимых полей в вычислимых полях
В связи в архитектурными особенностями генератора sql-запросов во Flexberry ORM, использование вычислимых полей внутри других вычислимых полей не поддерживается. При необходимости необходимо скопировать логику вычисления нужного поля:
Например, есть класс Дом с мастером Улица.
У класса Улица есть вычислимое поле NotStoredName, вычисляемое следующим образом:
'Улица ' + @Название@ + ' имеет первичный ключ ' +CAST(STORMMainObjectKey as varchar(max))
Пусть есть потребность использовать данное поле при вычислении значения вычислимого поля класса Дом:
'Корпус ' + @Корпус@ + ';' + @Улица.NotStoredName@
В описанном выше примере значение не будет подсчитано. Для реализации требуемой логики, рекомендуется расписать выражение, вычисляющее значение поля, следующим образом:
'Корпус ' + @Корпус@ + ';' + 'Улица ' + @Улица.Название@ + ' имеет первичный ключ ' +CAST(@Улица@ as varchar(max))