Введение
Вычислимые свойства - это специальные атрибуты объекта данных, значения которых вычисляются динамически на основе других свойств. Вычислимые свойства объекта не сохраняются в базе данных и их значение вычисляется через 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))