Перейти к содержанию

Представления

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

Вы используете представления:

  • Когда в рабочем месте используете любой реестр. Реестр - это представление.

  • Когда работаете с любым отчетом. Отчет - это одно или несколько представлений, связанных вместе.

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

Представления создаются, редактируются и отлаживаются в Tessa Admin.

Для представления указывается:

  • Метаинформация. Она определяет его возможности и поведение. Колонки, их порядок, их названия, поисковые параметры, подмножества, сортировки, ссылки и т.д.

  • Шаблон sql-запроса. При помощи специальных расширений языка SQL создается шаблон запроса, при помощи которого система генерирует нужный текст запроса в той или иной ситуации. Инструкции шаблонизатора позволяют гибко формировать текст запроса, достигая любого необходимого уровня оптимизации.

    Note

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

  • Роли, которые имеют доступ к данному представлению.

Tip

Поля “Запрос” и “Метаданные” поддерживают сочетание клавиш “Ctrl+Space”, вызывающее контекстное меню, из которого можно выбрать и вставить в текст шаблоны стандартных грамматических конструкций.

Представление формируется и выполняется на сервере приложений. Клиентское рабочее место передает только заданные поисковые параметры.

Формат sql-разметки

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

Типы операторов

Не блочные операторы:

#keyword

или

#keyword(....)

Блочный оператор допускают вложенные неблочные и блочные операторы. Содержимое внутреннего блока трактуется в зависимости от keyword

Блочные операторы:

#keyword{ ... }

или

#keyword(...){ ... }

Блочный оператор с блоком else. Оператор выбирает первый или второй блок в зависимости от условия. Ключевое слово else не используется.

#keyword(...){ основной блок ... } { else блок ... }

Между ключевым словом и скобками, а также между открывающей и закрывающей скобками любого типа могут быть whitespace, перевод строки:

#keyword ( ....

)

#keyword

(

)

#keyword { ... }

#keyword

{

...

}

Операторы

#view

Описывает основные параметры представления.

#view( DefaultSortColumn: ColAlias1 asc ColAlias2 desc, DefaultSortDirection: asc, Paging: no, PageLimit: 22, ExportDataPageLimit: 1000, RowCountSubset: SubsetAlias, MultiSelect: true, Appearance: ColumnAlias, SelectionMode: Row|Cell, EnableAutoWidth: true|false, QuickSearchParam: ParamAlias, GroupingColumn: ColAlias, AutoWidthRowLimit: 20, TreeId: RowID, TreeParentId: ParentRowID, TreeGroup: Group, TreeGroupId: GroupRowID, TreeGroupParentId: GroupParentRowID, TreeGroupDisplayValue: GroupCaption, ConnectionAlias: ConnAlias, TreatAsSingleQuery: false)

  • DefaultSortColumn - ColAlias1 [asc|desc] [ColAliasN [asc|desc]] - алиасы столбцов в запросе (столбцы должны быть определены через #column), по которым происходит сортировка по умолчанию. Что именно будет писать в запрос #order_by определяется из параметра SortBy этих столбцов. После имени столбца может быть указан порядок сортировки asc, desc. Столбцы и модификаторы сортировки если они указаны должны разделятся пробелом.

  • DefaultSortDirection - asc|desc - Направление сортировки по умолчанию. При этом desc - инвертирует текущий порядок сортировки указанный для столбца.

  • Paging - always|optional|no

    • always - пейджинг обязателен всегда.

    • optional - пейджинг может быть отключен (инженером при настройке рабочего места).

    • no - представление не поддерживает пейджинг.

  • PageLimit - размер страницы для данного представления. Имеет смысл, только если представление поддерживает пейджинг. Необязательное, по умолчанию = 20.

  • ExportDataPageLimit - размер страницы для выгрузки. Когда пользователь в клиенте выгружает все данные представления, система получает их на клиент постранично. Размер страницы определяется этим параметром. Это позволяет разработчику управлять нагрузкой на память клиента, каналы и сервер БД, если представление несложное с небольшим количеством столбцов и относительно небольшим количеством строк, можно написать 100 000 000 и все данные будут выгружены за один заход. В любом случае в данном параметре не имеет смысла ставить маленькие значения - выбирайте, начиная от 1000 и более. Необязательное, по умолчанию 1000.

  • RowCountSubset - псевдоним представления, используемого для расчета количества строк в представлении. Для подсчета количества элементов в программном запросе TessaViewRequest к представлению необходимо указать CalculateRowCounting = true. Если псевдоним не задан, или представление отображается не в режиме постраничного вывода, то для расчета количества строк используется число строк возвращенное запросом представления(ITessaResult.Rows.Count). Подмножество заданное в данном параметре является системным и становится не доступным для выбора пользователю в TessaClient и настройки его отображения через TessaAdmin.

  • MultiSelect – true|false - признак возможности выделения нескольких строк в представлении.

    • True –возможно выделить несколько строк.

    • False – возможно выделить одну строку (режим по умолчанию).

  • Appearance - алиас колонки в запросе, в которой описано оформление для текущей строки представления.

  • EnableAutoWidth - true|false - признак автоматического расчета ширины столбцов представления. Опциональный.

  • SelectionMode - режим выделения. Опциональный.

    • Row - режим выделения строки.

    • Cell - режим выделения ячейки.

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

  • GroupingColumn - псевдоним столбца для группировки строк таблицы по умолчанию. Необязательный.

  • TreeId - Имя столбца идентификатора строки в режиме дерева. Необязательный.

  • TreeParentId - Имя столбца идентификатора родительской строки в режиме дерева. Необязательный.

  • TreeGroup - Имя столбца, указывающего на то, что строка является группой в режиме группировки. Необязательный.

  • TreeGroupId - Имя столбца идентификатора строки в режиме группировки. Необязательный.

  • TreeGroupParentId - Имя столбца идентификатора строки родительской группы в режиме группировки. Необязательный.

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

  • AutoWidthRowLimit - Количество строк в результате выполнения представления, допускающее автоматический расчет ширины столбцов таблицы. Необязательное, если значение не указано явно в метаданных будет использовано значение из PageLimit.

  • ConnectionAlias - алиас строки подключения (из конфигурационного файла веб сервиса app.json) к БД, на которой будет выполняться представление вместо дефолтной базы. В конфигурационном файле можно указать подключение к любой СУБД. Если, например, основная база - MSSQL, а подключение к базе Postgres, то запрос генерируется по правилам Postgres; если же база какая-то другая (например, Oracle), то по умолчанию используются правила генерации для MSSQL.

    С помощью данного параметра можно прописать подключение к другой базе, в том числе не к базе Tessa, а, например, к какой-то другой информационнной системе.

    Note

    Для использования этой настройки требуется модуль лицензии “Кластеризация”. Он включён в лицензии Enterprise.

  • TreatAsSingleQuery - true|false - если надо генерировать хранимую процедуру (как в представлениях Postgres); false - если достаточно сразу выполнить запрос (как в MSSQL). Если не указан - false.

Tip

Примеры представлений в типовом решении с иерархическими группировками и/или с иерархией строк: TaskHistory, Groups, GroupsWithHierarchy, Hierarchy.

#param

Декларирует параметр поиска, на текст запроса не влияет. Внутри могут находиться операторы #autocomplete, #dropdown, #source_views.

#param( Alias: EmployeeID, Caption: $Views_Performer, Hidden: false, Type: uniqueidentifier, Multiple: true, RefSection: Performers, ConvertToUtc: true, AllowedOperands: Equality NonEquality, DisallowedOperands: Between Contains, HideAutoCompleteButton: false ) { #autocomplete(View: ViewAlias, Param: ParamAlias, PopupColumns: ColumnIndexes, RefPrefix: reference_prefix) #source_views(ViewAlias1, ViewAlias2, ...) #dropdown(View: ViewAlias, PopupColumns: ColumnIndexes, RefPrefix: reference_prefix) }

  • Alias - Алиас параметра (должен быть уникальным).

  • Caption - Текстовая метка для интерфейса поисковой формы.

  • Hidden - true|false - признак скрытого параметра. Скрытые параметры используются системой и не видны в поисковой форме. Необязательный. Если не указан - параметр видимый.

  • Type - Тип входящего значение для параметра (в терминах MS SQL Server).

  • Multiple - true|false - Может ли параметр быть задан несколько раз. Например, при поиске по сотруднику, можно указать нескольких сотрудников - в результат выборки попадет любой из них. Необязательный. Если не указан - параметр допускает множественный выбор.

  • RefSection - Алиас секции (по факту название таблицы), на которую ссылается данный параметр. Необязателен. Если указан, то система может:

    • Автоматически подобрать для параметра на форме поиска - источники данных, которые предоставляют ссылки на данный же тип секции (см. #reference)

    • Если где-то в UI фигурируют данные, являющиеся ссылкой на данную секцию (колонка во представлении или контрол на форме карточки), то система может сама подобрать представления, получающие на входе параметр данного типа и показать их в контекстном меню колонки или контрола. Например, в представлении по входящим есть колонка “Контрагент”. Правый клик по колонке покажет, что в системе есть представление “Все договора с контрагентом” (у которого есть параметр со ссылкой на контрагента - partner) и позволит сразу же вызвать это представление с фильтрацией по данному параметру.

  • ConvertToUtc - true|false - признак необходимости конвертации значения параметра в UTC для типов datetime и datetime 2. Необязательный, однако если его не задать для параметра, то при выводе значения в “баллончике фильтрации” дата из UTC будет конвертироваться в локальную, из-за чего она может “съехать” на сутки назад. Если не указан - true.

  • AllowedOperands – Список разрешенных типов фильтрации по данному параметру. Может иметь следующие значения: Between, Contains, StartWith, EndWith, Equality, NonEquality, GreatOrEquals, GreatThan, LessOrEquals, LessThan, IsNull, IsNotNull, IsTrue, IsFalse. Можно указать несколько значений разделяя их пробелом. Необязательный.

  • DisallowedOperands - Список запрещенных типов фильтрации по данному параметру. Может иметь следующие значения: Between, Contains, StartWith, EndWith, Equality, NonEquality, GreatOrEquals, GreatThan, LessOrEquals, LessThan, IsNull, IsNotNull, IsTrue, IsFalse. Можно указать несколько значений разделяя их пробелом. Необязательный.

  • HideAutoCompleteButton - true|false - признак скрытия кнопки выбора значения из диалогового окна. Необязательный. Если не указан - false.

Вложенные операторы #autocomplete, #dropdown. Используются для настройки элемента управления для выбора значения для параметра. Элемент управления представляет из себя автокомплит с кнопкой ....

#autocomplete - описывает представление, которое нужно использовать для автокомплита.

  • Может быть необязательным. По набору текста выполняется определенное представление View: Alias, набранный текст передается в заданный параметр представления Param: ParamAlias, результат выборки представления отображается в качестве таблички автокомплита.

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

  • Если #autocomplete не указан, то он не работает.

  • В параметре PopupColumns возможно указать список индексов колонок представления которые будут отображены в виде списка, индексация начинается с 0.

  • RefPrefix: reference_prefix позволяет указать, reference с каким именно префиксом нужно выбирать из представления, указанного в View. Выбирается первый референс, у которого в свойстве ColPrefix указано то же, что и в RefPrefix. Если не указан, то система выбирает ссылку из представления, ориентируясь на #param(RefSection: ...) - т.е. выбирает первую ссылку с таким же RefSection.

#dropdown - описывает представление, которое нужно использовать для вывода выпадающего списка в элементе управления автокомплитом.

  • Может быть необязательным.

  • В виде списка будут отображены значения из представления View: Alias, результат выборки представления отображается в качестве таблички автокомплита.

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

  • Если #dropdown не указан, то он не работает.

  • В параметре PopupColumns возможно указать список индексов колонок представления которые будут отображены в виде списка, индексация начинается с 0.

  • RefPrefix: reference_prefix работает аналогично #autocomplete(RefPrefix: ...) и определяет, какой именно референс будет выбран из строки представления, которую выбрал пользователь в dropdown списке.

#subset

Декларирует подмножество(подвыборку) из данного представления. Подмножество - это срез текущих данных представления (с учетом наложенных фильтров) по какому-то критерию. Например, для списка входящих получить список получателей, которым направлены эти входящие.

Подмножества в работе можно посмотреть на примере представления “Мои задания” в типовой конфигурации.

#subset( Alias: Employees, Caption: $Employees_Assigned, Kind: List, CaptionColumn: SqlColAlias, RefColumn: SqlColAlias, RefParam: ParamAlias, TreeRefParam: TreeParamAlias, TreeHasChildrenColumn: SqlColAlias, CountColumn: SqlColAlias, HideZeroCount: true )

  • Alias - Уникальный алиас Подмножества.

  • Caption - Читабельное название Подмножества.

  • Kind - List|Tree - Вид Подмножества - плоский список или дерево. Не обязательный, по умолчанию плоский список.

  • CaptionColumn - Алиас колонки из запроса, которая будет использована в качестве названия для UI.

  • RefColumn - Алиас колонки из запроса, которая будет использоваться как значение входящего параметра для фильтрации представления. Важно учитывать, что колонка может содержать null.

  • RefParam - Алиас параметра из текущего представления, в который будет передаваться референсное значение из RefColumn.

  • TreeRefParam - Алиас параметра из текущего представления, который будет использоваться для получения узлов дерева с определенным родителем. Для получения верхнего уровня, в параметр передается NULL, для получения дочерних узлов - в параметр передается значение из RefColumn, которое трактуется как идентификатор текущего узла дерева. Параметр обязателен при Kind: Tree.

  • TreeHasChildrenColumn - Алиас колонки из представления, которая должна содержать значение типа bit, которое трактуется как признак наличия дочерних узлов. Если значение = 1, система показывает плюсик для разворачивания элемента, если 0 - не показывает. Необязательное, если не задано, то система будет показывать плюсики у всех элементов дерева до первой попытки их развернуть, когда выяснится, есть или нет на самом деле у него дочерние элементы. Параметр обязателен при Kind: Tree.

  • CountColumn - Алиас колонки из запроса, которая должна содержать число и трактуется как количество элементов с соответствующим значением референсной колонки во представлении. Необязательный параметр.

  • HideZeroCount - Необязательный, по умолчанию false. Указывать, скрывать ли нулевые значение счетчиков, т.е. не отображать 0. Имеет смысл, только если задан CountColumn.

#column

Описывает одну из колонок в результате выполнения представления. Необязательная директива.

#column(Alias: ColumnAlias, Caption: $Employee, Hidden: false, SortBy: t.Column1 asc t.ColumnN desc, Localizable: true, MaxLength: 150, ConvertToLocal: true, Appearance: ColumnAlias, DisableGrouping:false)

  • Alias - Алиас должен совпадать с именем колонки в Select.

  • Caption - Читабельное название для колонки. Может быть необязательным, в этом случае для названия колонки в таблице будет использоваться алиас.

  • Hidden - true|false - Скрывать ли колонку во представлении. Необязательный, по умолчанию false.

  • SortBy - t.Column1 [asc|desc] [t.ColumnN [asc|desc]] - Выражение для сортировки\группировки вида table.Column. Может содержать список столбцов и модификаторов направления сортировки asc, desc разделенные пробелами.

  • Localizable - true|false - Текст в данной ячейке может содержать константы локализации. Они будут заменены на значения.

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

  • ConvertToLocal - true|false - Для колонки типа datetime определяет, нужно ли конвертировать ее значение в локальное время из UTC. Конвертация производится на клиенте. Необязательный, по умолчанию true.

  • Appearance - ColumnAlias|AppearanceAlias - Алиас колонки запроса с описанием оформления для текущей колонки или Алиас заданного в метаинформации оформления для текущей колонки.

  • DisableGrouping - true|false - Запрещать ли группировку строк таблицы по данному столбцу. Необязательный, по умолчанию false.

#reference

Описывает ссылку для связи данных с метаинформацией. Одна строка представления может представлять несколько различных ссылок. Например, ссылку на исполнителя и автора - обе будут иметь одинаковый RefSection.

Все поля, относящиеся к данной ссылке обязаны иметь один и тот же префикс. Он задается параметром ColPrefix.

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

Например, в некой карточке есть ссылка на сотрудника, автора. В схеме ссылочное поле Author и в нем два физических поля AuthorID, AuthorFullName. Пользователь из некоего представления выбирает строчку, содержащую ссылку на сотрудника с префиксом Pref: PrefID, PrefFullName. Далее, для заполнения полей в карточке, система в обоих случаях вычтет префикс из имени (вычтя префикс Pref, у нас остается ID, FullName, а вычтя название ссылочной колонки Author, остается тоже ID, FullName). Оставшееся будет использовано для маппинга - данные из какой колонки в какое поле карточки записать.

#reference( ColPrefix: Author, RefSection: PersonalRoles Roles, DisplayValueColumn: AuthorFullName, IsCard: true, OpenOnDoubleClick: true)

  • ColPrefix - Обязательное. Префикс, по которому система поймет, какие поля представления относятся к этой ссылке.

  • RefSection - Обязательное. Алиас секции (по факту таблицы), на которую ссылаемся. Это может быть секция карточки, а может быть перечислением через пробел имен секций(RefSection: Alias1 Alias2 AliasN). Секция может быть виртуальной и даже несуществующей в схеме.

  • DisplayValueColumn - Необязательное. Тут указывается алиас колонки, в которой лежит строковое название для ссылки. Если оно не задано, система сама попробует такую колонку найти, когда ей нужно строковое представление. Например, пройдет по всем колонкам этой ссылки и посмотрит тип у полей, выбрав первое же поле со строковым типом. Например, если ссылка на сотрудника, то здесь будет лежать название колонки, в которой лежит значение вида “Фамилия И.О.”. Может использоваться для контекстного меню, а также для открытия других представлений, у которых входящим параметром является ссылка того же типа - для отображаемого значения в контроле выбора значения параметра.

  • IsCard - true|false - Необязательное. Является ли эта ссылка ссылкой на карточку. Если является, то в контекстном меню строки представления можно эту карточку открыть. Одна строка представления может предоставлять несколько ссылок, при этом некоторые или все могут быть ссылками на карточку, при этом одна из них открывается по дабл клику, а остальные через контекстное меню или возможно, имеет смысл сделать открытие по дабл-клику именно на этой колонке (на колонке DisplayValueColumn, если она видима). Колонка с ключом ссылки почти наверняка будет всегда скрыта.

  • OpenOnDoubleClick - true|false - Если true, то именно эту ссылку нужно открывать по умолчанию, при этом это должна быть карточка, т.е. IsCard: true. Необязательный, по умолчанию false.

#appearance и кастомизированное оформление строки и ячейки представления

В Tessa вы можете оформить строки или ячейки представления различными стилям, задать разный фон, шрифт, цвета и т.д. для разных строк или колонок. Оформление задается в виде строки вида.

#appearance( Alias: AppearanceAlias, Foreground: Black, Background: Transparent, FontFamily: font, FontFamilyUri: fontUri, FontSize: 10, FontStretch: normal, FontStyle: normal, FontWeight:normal, ToolTip:toolTipText, HorizontalAlignment:Center, VerticalAlignment:Center, TextAlignment:Center)

  • Foreground, Background - цвет шрифта и фона, задается как имя цвета, например Yellow или компонентно с альфа-каналом (слева-направо #ПрозрачностьКрасныйЗеленыйСиний в шестнадцатеричном формате), например #A0FF0000.

  • Tooltip - текст подсказки к ячейке или строке, будет появляться через долю секунды после наведения мыши.

  • HorizontalAlignment, VerticalAlignment, TextAlignment - выравнивание, применяемое к элементу.

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

Это описание можно задать:

  • Прямо в мета-информации представления, в этом случае нужно задать Alias. Например, вы подсвечиваете строки всего двумя цветами, в этом случае можно задать два оформления с именами RedColumn, GreenColumn. Далее в выборке в колонке с именем, скажем, ColumnColor вы в зависимости от данных в других колонках - формируете строку “RedColumn” или “GreenColumn”. И затем имя этой колонки используете в #view(Appearance:ColumnColor) для подсветки целых строк или в #column(Appearance:ColumnColor) для подсветки конкретной колонки.

  • Сформировать в какой-то колонке выборки как строку вида “#appearance(Foreground: Black, Background: Yellow)” и далее использовать колонку в #view(Appearance:ColumnAlias) для подсветки целых строк или в #column(Appearance:ColumnAlias) для подсветки конкретной колонки. В этом случае вы можете использовать гораздо более гибкое оформление.

Для использования подсветки есть два способа:

  1. Задать #view(Appearance: ColumnName). В указанной колонке представления либо алиас заданного в метаинфе оформления, либо строка “#appearance(..). В результате вся строка представления оформляется в указанном стиле.

  2. Задать #column(Appearance: ColumnOrAppearanceAlias). Если указан алиас, то вся колонка целиком будет оформлена в указанном стиле. Если указано имя колонки с оформлением, то в этой колонке либо алиас заданного в метаинфе оформления, либо строка “#appearance(..) и тогда каждая строка этой колонки будет оформлена в стиле, заданном в колонке с оформлением.

Note

Настройки #appearance заменяются друг друга, а не дополняют. Сначала используются настройки колонки, если они есть, потом настройки строки из данных отдельной колонки или заданные в метаинформации представления. Например, если в настройках строки указана повышенная жирность, а в настройках колонки - цвет фона, то вся строка будет жирной, кроме ячеек из колонки, где окрашивается цвет фона. В колонке также должна быть указана повышенная жирность, чтобы вся строка выводилась жирным шрифтом.

Tip

Вы можете посмотреть пример в представлении MyTasks (Мои задания), где в зависимости от степени просроченности задания оно подсвечивается красным цветом разной степени прозрачности.

Условный оператор #if и другие

Important

В настоящий момент рекомендуется всегда использовать оператор #if. Операторы #if_def и прочие оставлены в платформе для совместимости.

  • HEADER IS NOT A PARAGRAPH!:

    Текст внутри блока ... text ... будет вставлен в представление если условие expression истинно, в противном случае в представление будет вставлен текст ... else text ...

    Выражение expression соответствует синтаксису, используемому в C#.

    В выражении можно использовать алиасы параметров, подвыборок, функции и классы .Net Core.

    Проверка наличия на входе параметра или Подмножества с алиасом Param1:

    #if(Param1) {... text ...}

    Проверка отсутствия на входе параметра или Подмножества с алиасом Param1:

    #if(!Param1) {... text ...} {... else text ...}

    Проверка по значению параметра:

    #if(Param1 && Param1.Value == 10) { ... text...} { ... else text ...}

    Проверка наличия заданных параметров:

    #if(any) { ... text ...}

    Проверка наличия сортировки по определенной колонке

    #if(request.SortedBy("Alias"))

    Проверка порядка сортировки по определенной колонке. Этот и предыдущий пример полезны, если нужно осуществить супер-сложную сортировку при щелчке по какой-то колонке. Метод SortDirection возращает “asc”"desc”\null.

    #if(request.SortDirection("Alias") == "asc")

    Проверка количества колонок в сортировке (если вы щелкнете по заголовку колонки с нажатой кнопкой Shift, то эта колонка добавится к уже заданной)

    #if(request.SortingColumns.Count == 1)

    Проверка заданного поискового параметра TypeId и с каким именно критерием он используется

    #if (TypeID && TypeID.CriteriaName == "IsNull")

    Использование алиасов в условном операторе #if

    • Alias – Псевдоним параметра или Подмножества.

    • Alias.Value – Получение единственного значения, единственного условия для параметра с именем Alias

    • Alias.Value1 – Получение первого значения единственного условия для параметра с именем Alias

    • Alias.Value2 – Получение второго значения единственного условия с именем Alias

    • Alias.ValueIsNull, Alias.Value1IsNull, Alias.Value2IsNull - Возвращает true, если параметр с именем Alias задан, но при этом значение в диалоге фильтрации не выбрано.

    • Alias.ValueCount – Получение количества установленных значений для параметра с именем Alias.
      Возможные значения:

      • -1 - Параметр не задан или задано более одного условия

      • 0 - Задано условие которые не поддерживает задание значений для параметров(IsNull, IsNotNull, IsTrue и т.п.)

      • 1 - Задано одно значение (условия Equals, Not Equals и т.п.)

      • 2 - Задано два значения (условие Between)

    • Alias.CriteriaCount – Получение количества в заданных условиях для параметра Alias. Если параметр не задан возвращает – 1, в противном случае количество условий.

    • Alias.CriteriaName – Возвращает имя первого условия или пустую строку если условие не задано.

  • HEADER IS NOT A PARAGRAPH!:

    #if_def(Alias1,Alias2, ...) { ... text ... }

    Important

    Устаревший, используйте #if.

  • HEADER IS NOT A PARAGRAPH!:

    Текст внутри блока ...text... будет вставлен в представление, если на входе определен указанный алиас параметра или Подмножества или их набор.

  • HEADER IS NOT A PARAGRAPH!:

    #if_def_else(Alias1, Alias2, ...) { ... text ... } { ... else text ... }

    Important

    Устаревший, используйте #if.

  • HEADER IS NOT A PARAGRAPH!:

    Текст внутри блока ...text... будет вставлен в представление, если на входе определен указанный алиас параметра или Подмножества или их набор. В противном случае в представление будет вставлен текст ... else text ...

  • HEADER IS NOT A PARAGRAPH!:

    #if_not_def_else(Alias1, Alias2, ...) { ... text ... } { ... else text ... }

    Important

    Устаревший, используйте #if.

  • HEADER IS NOT A PARAGRAPH!:

    Текст внутри блока ...text... будет вставлен в представление, если на входе не определен указанный алиас параметра или Подмножества или их набор.

    В противном случае в представление будет вставлен текст ... else text ...

В условных операторах допускается возможность использования следующих специальных алиасов.

Administrator
Алиас определен, если пользователь, для которого происходит построение представления, является администратором
Subset Алиас определен, если построение происходит в режиме Подмножества
Normal Алиас определен, если построение происходит в режиме представления.

#param_expr

Используется для подстановки в тех местах, где требуется операнд фильтрации по параметру в запросе. Разворачивается в пустую строку, если параметр не задан. #param является синонимом #param_expr, как более короткая форма записи и может использоваться в тексте запроса на равне с ним.

#param_expr(EmployeeID, t2.ID)

Разворачивается в конструкцию вида and @EmployeeID_1 = t2.ID или and @EmployeeID_1 = t2.ID OR @EmployeeID_2 = t2.ID и т.д., если значений несколько с учетом типа входящих данных.

#param_expr(EmployeeID)

Разворачивается в конструкцию вида @EmployeeID_1. Используется, если по каким-то причинам нужно получить в запросе реальный параметр SQL SErver и самим построить выражение фильтра или поиска по нему.

#columns

#columns { Col1, Col2, Col3 }

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

Note

Аналогичным образом работает #if(Normal)

#order_by

#order_by

Формирует текущий список сортировки вида t1.Column1 asc, t2.Column2 desc для вставки в запрос. В случае, если представление выполняется в режиме Подмножества, разворачивается в пустую строку.

#extension

#extension(TypeName: Имя типа, Order:Порядковый номер)

Описывает расширение, применяемое к представлению в TessaClient. Необязательный. Описывается в метаданных представленя.

Расширение является классом C# реализующим интерфейс IWorkplaceExtension<in TModel> и зарегистрированным в реестре расширений IWorkplaceExtensionRegistry.

Атрибуты

  • Имя типа - Обязательное. Валидное имя типа зарегистированное в реестре расширений. Например Tessa.Extensions.Default.Client.Views.CustomButtonWorkplaceComponentExtension.

  • Order – Обязательное. Порядковый номер применения расширения к представлению.

Note

Примеры реализации расширений можно увидеть в пространстве имен Tessa.Extensions.Default.Client.Views проекта Tessa.Extensions.Default.Client

#eval

#eval(выражение на C#)

Позволяет использовать выражения C# в тексте запроса. Значение полученное в результате выполнения записывается в формируемый текст запроса. Внутри текста выражение возможно использовать обращение к параметрам запроса аналогичное используемому в #if.

#var

#var(VariableName: выражение на C#)

Позволяет создать именованную переменную в тексте запроса, присваивая ей результат выражения. В дальнейшем переменная может использоваться в конструкциях `#var,#if,#eval`. Имя переменной должно быть валидным именем C#. Переменная должна быть описана перед первым использованием, желательно в начале текста запроса. Тип значения переменной выводится на основании результата выражения заданного в ней.

Примеры использования:

#var(MyVariable: Param1 || !Param2 || Param3 && Param3.CriteriaName == "Equality") #var(CurrentDateTime: DateTime.UtcNow) #var(Message: "Текущее время:" + CurrentDateTime.ToString()) ... #if(OtherParam && MyVariable) { #eval(Message) }

Переменную можно переопределять в зависимости от условий, помещать внутрь блока #if. В операторах #var, так же, как и в #eval, можно ссылаться на значение ранее определенных переменных по их имени.

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

#var(var1:Param1.Value)

#if(Param2) { #var(var1:var1 + Param2.Value) }

Select '#eval(var1)'

Подмножества (SubSet)

Подмножества предназначены для построения различных связанных выборок на основе данных представления.

Например, для представления Мои задания может быть определена подвыборка По контрагенту - она вернет список всех контрагентов, которые указаны в карточках, по которым у вас есть задания. Подвыборка выполняется всегда с учетом текущих заданных параметров представления. Если представление отфильтровано по Статус = Не начато, то выборка контрагентов вернет только тех контрагентов, которые указаны в карточках с неначатыми заданиями.

Пример представления, показывающего список ролей, с подмножеством по типу роли

#view(DefaultSortColumn: RoleName, DefaultSortDirection: asc, Paging: always, RowCountSubset: Count) #column(Alias: RoleID, Hidden: true) #column(Alias: RoleName, Caption: Роль, SortBy: r.Name) #column(Alias: TypeName, Caption: Тип роли, Localizable: true) #column(Alias: rn, Hidden: true) #param(Alias: Name, Caption: Имя, Hidden: false, Type: nvarchar, Multiple: true) #param(Alias: TypeID, Caption: Тип, Hidden: false, Type: int, Multiple:false, RefSection: RoleTypes) { #autocomplete(View: RoleTypes, Param: RoleTypeNameParam, RefColumn: RoleTypeID, PopupColumns: 1) #dropdown(View: RoleTypes, PopupColumns: 1) } #reference(ColPrefix: Role, RefSection: Roles, DisplayValueColumn: RoleName, IsCard: true, OpenOnDoubleClick: true)

#subset(Alias: RoleTypes, Caption:$Views_Roles_ByType, CaptionColumn: Name, RefColumn: ID, RefParam: TypeID) #subset(Alias: Count)

select * from ( select #if(Normal) { r.ID as RoleID, r.Name as RoleName, r.TypeID as TypeID, rt.Name as TypeName, row_number() over (order by #order_by) as rn } #if(RoleTypes) { /*Выборка, которая выполняется в режиме подмножества по типам ролей*/ distinct rt.ID, rt.Name } #if(Count) { count(*) as cnt } from Roles r with(nolock) inner join RoleTypes rt with(nolock) on rt.ID = r.TypeID where r.TypeID <> 6 /* Не показываем временные роли заданий */ and r.Hidden = 0 /* Не показываем скрытые роли */ #param_expr(TypeID, r.TypeID) /* В этот параметр приходит идентификатор типа, выбранного пользователем в подмножестве*/ #param_expr(Name, r.Name)

) t #if(PageOffset) { where t.rn >= #param_expr(PageOffset) and t.rn < (#param_expr(PageOffset) + #param_expr(PageLimit)) order by t.rn }

И результат

Подмножества могут быть трех разных типов:

  • В списке показывается некое отображаемое значение, а фильтрация идет по скрытому идентификатору (в этом случае подвыборка возвращает две колонки - идентификатор и отображаемая строка). Такое подмножество RoleTypes на примере выше.

  • В списке показывается то же самое значение по которому идет фильтрация (в этом случае выборка может возвращать одну и ту же колонку)

  • В списке показывается в дополнение к значениям еще и количество строк с этим значением (в этом случае выборка возвращает дополнительную строковую колонку).

Система сама понимает, какой из вариантов (в зависимости от параметров, указанных в #subset и формирует соответствующий пользовательский интерфейс.

Например, вариант предыдущего представления с количеством ролей различных типов:

#view(DefaultSortColumn: RoleName, DefaultSortDirection: asc, Paging: always, RowCountSubset: Count)
#column(Alias: RoleID, Hidden: true)
#column(Alias: RoleName, Caption: Роль, SortBy: r.Name)
#column(Alias: TypeName, Caption: Тип роли, Localizable: true)
#column(Alias: rn, Hidden: true)
#param(Alias: Name, Caption: Имя, Hidden: false, Type: nvarchar, Multiple: true)
#param(Alias: TypeID, Caption: Тип, Hidden: false, Type: int, Multiple:false, RefSection: RoleTypes)
{
    #autocomplete(View: RoleTypes, Param: RoleTypeNameParam, RefColumn: RoleTypeID, PopupColumns: 1)
    #dropdown(View: RoleTypes, PopupColumns: 1)
}
#reference(ColPrefix: Role, RefSection: Roles, DisplayValueColumn: RoleName, IsCard: true, OpenOnDoubleClick: true)

#subset(Alias: RoleTypes, Caption:По типу роли, CaptionColumn: Name, RefColumn: ID, RefParam: TypeID, CountColumn: cnt) /* (1) */
#subset(Alias: Count)
  1. Обратите внимание, что теперь мы указываем, в какой колонке запроса лежит количество.

Выноски:

  1. Обратите внимание, что теперь мы указываем, в какой колонке запроса лежит количество.
select *
from
(
    select
        #if(Normal) { 
        r.ID as RoleID,
        r.Name as RoleName,
        r.TypeID as TypeID,
        rt.Name as TypeName,
        row_number() over (order by #order_by) as rn
        }
        #if(RoleTypes) { /*Выборка, которая выполняется в режиме подмножества по типам ролей*/
            rt.ID,
            rt.Name,
            count(*) as cnt                          /* (1) */
        }
        #if(Count) {
        count(*) as cnt
        }
    from Roles r with(nolock) inner join 
          RoleTypes rt with(nolock) on rt.ID = r.TypeID
    where r.TypeID <> 6 /* Не показываем временные роли заданий */
            and r.Hidden = 0 /* Не показываем скрытые роли */
            #param_expr(TypeID, r.TypeID) /* В этот параметр приходит идентификатор типа, выбранного пользователем в подмножестве*/
            #param_expr(Name, r.Name)
    #if(RoleTypes) { /*Теперь мы хотим группировку*/ /* (2) */
    group by rt.ID, rt.Name
    }
) t
#if(PageOffset) {
where t.rn >= #param_expr(PageOffset) and t.rn < (#param_expr(PageOffset) + #param_expr(PageLimit))
order by t.rn
}
  1. Считаем количество

  2. Не забываем сделать группировку в режиме этого подмножества

Выноски:

  1. Считаем количество

  2. Не забываем сделать группировку в режиме этого подмножества

Результат

Древовидные подмножества

Также, могут быть древовидные сабсеты. Они отличаются от обычных тем, что представлены в виде дерева. На верхнем уровне список значений - верхние узлы дерева, и каждый элемент сабсета можно не только щелкнуть (чтобы отфильтровалось представление), но и развернуть (если есть дочерние), в этом случае система показывает список дочерних узлов Подмножества.

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

  • Kind: Tree

  • TreeRefParam: ParamAlias. Алиас параметра для выборки узлов дерева по родительскому.

  • TreeHasChildrenColumn: ColumnAlias. Алиас колонки выборки, которая содержит признак наличия дочерних узлов (0,1).

В этом случае представление работает так:

  1. Когда пользователь разворачивает сабсет, система выполняет сабсет с заданным параметром TreeRefParam = NULL. Получая таким образом верхние узлы дерева.

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

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

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

Note

В типовой конфигурации древовидные подмножества используются в представлении “Департаменты\Departments”.

Контекстные параметры

Система автоматически определяет следующие параметры, зависящие от контекста

  • CurrentUserID - идентификатор текущего пользователя

  • PageOffset - если для представления включен пейджинг (зависит от настроек представления), то эта переменная определена и содержит индекс первой строки, которую нужно вернуть.

  • PageLimit - аналогично предыдущему, переменная содержит количество строк, которые нужно вернуть.

  • Locale - ID локали текущего пользователя

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

Корректная реализация постраничного вывода

Параметры постраничного вывода(пейджинга)

  • PageOffset - номер строки с которой необходимо начать получение данных.

  • PageLimit - количество строк которые система пытается получить от представления.

При использовании для получения номеров строк функции SQL Server row_number() нумерация строк начинается с 1.

Для корректной реализации пейджинга в представлениях следует использовать следующие конструкции.

where t.rn >= #param_expr(PageOffset) and t.rn < (#param_expr(PageOffset) + #param_expr(PageLimit))

или

where t.rn between #param_expr(PageOffset) and (#param_expr(PageOffset) + #param_expr(PageLimit)-1)

Warning

Система всегда запрашивает у представления на 1 строку больше, чем видит пользователь. Это позволяет определить, существует ли следующая страница данных. Например, если страница - 20 строк, то представление будет запрашивать 21 строку. Если ему вернулась 21 строка, система покажет пользователю 20 и будет доступен переход на следующую страницу. Если вернулось 20 или меньше - это последняя страница.

Отладка представлений

При разработке представлений можно пользоваться различными возможностями отладки.

Быстрый просмотр

На вкладке “Просмотр” можно сразу же посмотреть в интерфейсе пользователя данные, возвращаемые представлением.

Warning

Не забывайте, что если в представлении есть подмножество Count, то оно тоже будет выполняться. Если система вам показывает ошибку в текст, но вы не понимаете ее источник и текст верный - возможно проблема в некорректном тексте запроса в режиме подмножества Count.

Отладка

На вкладке “Отладка” можно посмотреть, какой именно текст запроса генерирует платформы в различных ситуациях, при различных параметрах или в режимах различных подмножеств.

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

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

Программное использование и расширение представлений.

Существуют следующие возможности использования и расширения представлений:

  • Вызов представления на клиенте или на сервере

  • Создание программных представления на клиентской стороне.

  • Создание программных представлений не серверной стороне.

  • Перехват обработки представлений на серверной стороне

Пример выполнения представления из кода

/* * Вызов представления контрагентов для поиска подходящих по имени * Код будет одинаковым и на клиенте и на сервере. Разница будет только в * наборе доступных представлений. На клиент доступны только те, на которые * у текущего пользователя есть права, на сервере - все имеющиеся. */

// Получаем ссылку на объект IViewService, например, через параметры конструктора // MyExtensionContructor(IViewService viewService, ...), который затем сохраняем в // поле нашего класса

// Получаем представление Контрагенты по его алиасу var view = await viewService.GetByNameAsync("Partners"); // Находим параметр Name var paramMetadata = view.Metadata.Parameters.FindByName("Name"); // Начинаем формировать реквест var request = new TessaViewRequest(view.Metadata); // Добавляем новый критерий поиска - по параметру Name, используем оператор "Содержит" var parameter = new RequestParameterBuilder() .WithMetadata(paramMetadata) .AddCriteria(new ContainsCriteriaOperator(), name, name) .AsRequestParameter(); request.Values.Add(parameter);

// Указываем системные параметры - нам нужно 10 строк начиная с 1-й // Также можно резолвить в конструкторе объект через интерфейс IViewSpecialParameters var specialParameters = new ViewPagingParameters(); specialParameters.ProvidePageLimitParameter(request.Values, Paging.Always, 10, false); specialParameters.ProvidePageOffsetParameter(request.Values, Paging.Always, 1, 10, false);

// Выполняем реквест var result = await view.GetDataAsync(request);

// Проверяем наличие данных в результате if (!result.Rows.Any()) { return; }

// Определяем индексы колонок с нужными нам алиасами var columns = result.Columns.Cast<string>().ToList(); var idIndex = columns.IndexOf("PartnerId"); var fullNameIndex = columns.IndexOf("FullName");

// Нас интересует первая строка результата. Строка - это массив значений колонок. var firstRow = (IList<object>)result.Rows.First();

// Получаем из первой строки результата идентификатор и имя контрагента var partnerId = (Guid)firstRow[idIndex]; var partnerName = (string)firstRow[fullNameIndex];

// Используем результат...

Примеры программных расширений для представлений

Info

  • В Tessa Client в папке Администратор\Расширения представлений\Список файлов. Это программное представление, которое показывает список файлов (и папок), расположенных на сервере приложений системы в папке c:\temp\1. Обратите внимание, что представление представляет подмножество, показывающее дерево подпапок в этой папке, а также поисковый параметр.

    Note

    Алиас этого представления ViewFiles (см. в Tessa Admin), исходный код доступен в файле Extensions\Tessa.Extensions.Default.Server\Views\ViewsIntercepter.cs, регистрация данного расширения выполняется в методе _Extensions\Tessa.Extensions.Default.Server\ServerApplicationConfigurator.cs#RegisterViews_.

  • В Tessa Client в папке Администратор\Расширения представлений\Генератор. Это представление по умолчанию ничего не показывает. Но как только вы зададите его поисковые параметры “Текст” и “Количество” - оно тут же покажет столько строк, сколько вы указали в количестве с текстом, указанным в параметре.

    Note

    Данное представление не видно в Tessa Admin, оно полностью генерируется на сервере, исходный код доступен в файле Extensions\Tessa.Extensions.Default.Server\Views\ViewsExtraProvider.cs, регистрация данного расширения выполняется в методе _Extensions\Tessa.Extensions.Default.Server\ServerApplicationConfigurator.cs#RegisterViews_.

Описание интерфейса ITessaView

/// <summary> /// Базовый интерфейс представления. /// Предназначен для имплементации представлений. /// Представления - произвольные источники данных /// позволяющие выполнять к ним <see cref="ITessaViewRequest">запросы</see> /// на получение <see cref="ITessaViewResult">данных</see>. /// Представление содержит <see cref="IViewMetadata">метаданные</see> /// описывающие возможные параметры запроса к представлению /// и детали визуализации результата. /// </summary> public interface ITessaView { #region Public Properties

/// <summary> /// Gets метаданные представления /// </summary> [NotNull] IViewMetadata Metadata { get; }

#endregion

#region Public Methods and Operators

/// <summary> /// Выполняет получение данных из представления /// на основании полученного <see cref="ITessaViewRequest">запроса</see> /// </summary> /// <param name="request"> /// Запрос к представлению /// </param> /// <param name="cancellationToken"> /// Объект, посредством которого можно отменить асинхронную задачу /// </param> /// <returns> /// <see cref="ITessaViewResult">Результат</see> выполнения запроса /// </returns> [NotNull] Task<ITessaViewResult> GetDataAsync(ITessaViewRequest request, CancellationToken cancellationToken = default);

#endregion }

Описание интерфейса ITessaViewAccess.

/// <summary> /// Интерфейс предоставляющий информацию о доступе к представлению. /// </summary> public interface ITessaViewAccess { #region Public Properties

/// <summary> /// Gets метаданные представления /// </summary> [NotNull] IViewMetadata Metadata { get; }

#endregion

#region Public Methods and Operators

/// <summary> /// Возвращает список ролей, которые необходимы для доступа к /// представлению, /// реализующему данный интерфейс /// </summary> /// <returns> /// Список ролей /// </returns> [NotNull] IEnumerable<Role> GetRoles();

#endregion }

Описание интерфейса IExtraViewListProvider

/// <summary> /// Интерфейс возвращающий список программных представлений /// </summary> public interface IExtraViewListProvider { #region Public Methods and Operators

/// <summary> /// Gets Алиас представления. Серверное представление с данным алиасом будет запрошено /// у сервиса представлений Tessa.Views.IViewService и передано в метод Tessa.Views.ITessaViewOverlay.InitOverlay(Tessa.Views.ITessaView) /// при инициализации списка доступных представлений на клиенте /// </summary> /// <returns>Список программных представлений</returns> [NotNull] IEnumerable<ITessaView> GetExtraViews();

#endregion }

Описание интерфейса ITessaViewOverlay

/// <summary> /// Описание интерфейса предназначенного для расширенной реализации клиентских программных /// представлениях Классы реализующие данный интерфейс получают возможность а) Получить /// ссылку на серверное представление с алиасом Tessa.Views.ITessaViewOverlay.ViewAlias /// б) Осуществить проверку необходимости регистрации клиентского представления /// </summary> public interface ITessaViewOverlay : ITessaView { #region Public Methods and Operators

/// <summary> /// Gets Алиас представления. Серверное представление с данным алиасом будет запрошено /// у сервиса представлений Tessa.Views.IViewService и передано в метод Tessa.Views.ITessaViewOverlay.InitOverlay(Tessa.Views.ITessaView) /// при инициализации списка доступных представлений на клиенте /// </summary> [NotNull] string ViewAlias { get; }

/// <summary> /// Осуществляет выполнение запроса на инициализацию клиентского представления. Возвращает /// признак необходимости замены регистрации представления в сервисе представлений. /// </summary> /// <param name="view"> /// Серверное представление или null, в случае если представление с алиасом Tessa.Views.ITessaViewOverlay.ViewAlias /// не полученно с сервера в виду отсутствия представления или отсутствия прав у /// текущего пользователя на запрошенное представление /// </param> /// <returns> /// True - Необходимо зарегистрировать данное представление в сервисе представлений, /// False - Представление не требуется регистрировать в сервисе представлений. /// </returns> bool InitOverlay([CanBeNull] ITessaView view);

#endregion }

Описание интерфейса IViewInterceptor

/// <summary> /// Интерфейс перехватчика представлений /// </summary> public interface IViewInterceptor { #region Public Properties

/// <summary> /// Gets список обрабатываемых представлений /// </summary> [NotNull] string[] InterceptedViews { get; }

#endregion

#region Public Methods and Operators

/// <summary> /// Осуществляет выполнение запроса на получение данных /// </summary> /// <param name="request"> /// Запрос /// </param> /// <param name="cancellationToken"> /// Объект, посредством которого можно отменить асинхронную задачу /// </param> /// <returns> /// Результат обработки /// </returns> Task<ITessaViewResult> GetDataAsync(ITessaViewRequest request, CancellationToken cancellationToken = default);

/// <summary> /// Вызывает инициализацию перехватчика, передавая в него /// список перехватываемых представлений <paramref name="overlayViews"/> /// </summary> /// <param name="overlayViews"> /// Список перехватываемых представлений /// </param> void InitOverlay([NotNull] IDictionary<string, ITessaView> overlayViews);

#endregion }

Создание программных представлений на клиентской стороне.

Для создания представления на клиентской стороне необходимо реализовать представление в виде класса, имплементирующего интерфейс ITessaView. Для того чтобы клиентское представление стало доступно на клиенте через сервис представлений IViewService, необходимо осуществить его регистрацию в контейнере приложения. При регистрации в контейнере необходимо указать алиас представления, под которым оно будет доступно системе. Регистрация клиентских представлений осуществляется в классе Registrator. представление должно регистрироваться с уникальным именем желательно совпадающим с алиасом представления, в противном случае в системе будет зарегистрировано представление последним осуществившее регистрацию в контейнере приложения. В системе представление будет доступно под алиасом определенным в его метаданных. В случае если на серверной стороне было объявлено представление с таким же алиасом, то представление на клиентской стороне замещает его и все запросы к представлению будут обрабатываться клиентским представлением.

В случае необходимости осуществления вызовов замещаемого клиентским представлением серверного представления, необходимо реализовать в клиентском представлении интерфейс ITessaViewOverlay. Реализация данного интерфейса в представлении предоставляет возможность получения серверного представления в метода InitOverlay, а также позволяет указать системе на необходимость регистрации клиентского представления в сервисе представлений.

Пример регистрации клиентского представления в контейнере приложения

public override void RegisterUnity() { this.UnityContainer .RegisterType<ITessaView, ClientProgramView>(nameof(ClientProgramView), new ContainerControlledLifetimeManager()) ; }

Note

Пример реализации клиентского программного представления можно увидеть в Tessa.Extension.Default.Client.Views.ClientProgramView.cs

Создание программных представлений на серверной стороне

Для создания представления на серверной стороне необходимо реализовать представление в виде класса, имплементирующего интерфейс ITessaView. В отличии от клиентских представлений серверные представления доступны как на серверной стороне, так и на клиентской. В случае необходимости разграничения доступа к серверному представлению существует возможность ограничить возможность вызова представления определенными ролями. Для реализации разграничения доступа необходимо реализовать в классе представления метод GetRoles() интерфейса ITessaViewAccess, возвращающий список ролей, которым доступно представление.

Для того чтобы серверное представление стало доступно через сервис представлений IViewService, необходимо осуществить его регистрацию в системе. Регистрация серверных представлений осуществляется не напрямую в контейнере, а через класс, реализующий интерфейс IExtraViewListProvider. Класс, реализующий данный интерфейс регистрируется в контейнере приложения с помощью ServerApplicationConfigurator. При регистрации в контейнере нескольких классов IExtraViewListProvider система будет использовать представления, предоставляемые классом, осуществившим регистрацию последним.

Note

Пример реализации поставщика серверных представлений и примеры реализации самих серверных представлений доступны в Tessa.Extension.Default.Server.Views.ViewsExtraProvider.cs

Перехват обработки представлений на серверной стороне.

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

Note

Примеры перехватчиков можно посмотреть в Tessa.Extension.Default.Server.Views.ViewsInterceptor.cs и Tessa.Extension.Default.Server.Views.ExampleInterceptor.cs.

Получение текста SQL-запроса на серверной стороне сформированного представлением

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

Для получения текста запроса необходимо привести представление, полученное от IViewsService на серверной стороне к интерфейсу IViewTextGenerator. Текст запроса возвращается методом TryGenerate. В случае если представление не поддерживает генерацию текста SQL-запроса будет возвращена пустая строка, в случае успешного исполнения будет возвращен текста запроса, сформированный по запросу, переданному в метод TryGenerate. Для выполнения запроса можно для текущего соединения можно получить из контейнера IViewQueryExecutor и вызвать его метод Execute.

/// <summary> /// Описание интерфейса генератора текста sql запроса представления /// <see cref="ITessaView" /> /// по запросу к представлению <see cref="ITessaViewRequest" />. /// </summary> public interface IViewTextGenerator { #region Public Methods and Operators

/// <summary> /// Осуществляет попытку генерации текста SQL запроса к представлению /// по запросу <paramref name="request"/>. Если представление не /// существует или /// не поддерживает генерацию текста запроса (программные представления), /// то будет возвращена пустая строка. /// Экземпляр представления по которому необходимо /// сгенерировать текст запроса /// выбирается из <paramref name="request.Alias"/> /// </summary> /// <param name="request"> /// Запрос к представлению /// </param> /// <returns> /// Сгенерированный текст запроса или пустая строка /// </returns> [NotNull] string TryGenerate([NotNull] ITessaViewRequest request);

#endregion }

Создание отчета с помощью связанных представлений

Для создания сложных отчетов можно воспользоваться механизмом связанных представлений. С их помощью можно на одной странице показывать данные нескольких представлений, связывать представления для отчетов мастер-дитейл, а также строить деревья зависимых представлений, отображая их в системе вкладок. Общая информация по связнным представления дана в Руководстве администратора, в данном разделе дается пример создания такого отчета.

Создадим два простых представления – представление, показывающее количество текущих заданий по типам и представление, показывающее информацию по текущим заданиям.

Представление количества текущих заданий по типам (ExampleCurrentTasksByType):

Метаинформация:

#view(DefaultSortColumn: TypeCaption, DefaultSortDirection: desc, Paging: always) #column(Alias: TypeCaption, Caption: Тип, Hidden: false) #column(Alias: cnt, Caption: Количество, Hidden: false) #param(Alias: RolaNameParam, Caption: Имя роли, Hidden: false, Type: nvarchar) #param(Alias: TypeNameParam, Caption: Тип задания, Hidden: false, Type: nvarchar)

Запрос

SELECT TypeCaption AS TypeCaption ,COUNT(TypeCaption) AS cnt FROM Tasks WHERE 1=1 #param_expr(RolaNameParam, Tasks.RoleName) #param_expr(TypeNameParam, Tasks.TypeCaption) GROUP BY TypeCaption

Представление отображает количество заданий каждого типа, для которого есть задания:

Представление по заданиям (ExampleCurrentTasks):

Метаинформация:

#view(DefaultSortColumn: TaskType, DefaultSortDirection: desc, Paging: always) #column(Alias: CardID, Hidden: true) #column(Alias: TaskID, Hidden: true) #column(Alias: TaskType, Caption: Тип, Hidden: false) #column(Alias: RoleName, Caption: Назначено, Hidden: false) #param(Alias: RolaNameParam, Caption: Имя роли, Hidden: false, Type: nvarchar) #param(Alias: TypeNameParam, Caption: Тип задания, Hidden: false, Type: nvarchar)

Запрос

SELECT Tasks.ID AS CardID ,Tasks.RowID AS TaskID ,Tasks.TypeCaption AS TaskType ,Tasks.RoleName AS RoleName FROM Tasks LEFT JOIN TaskCommonInfo ON Tasks.RowID = TaskCommonInfo.ID WHERE 1 = 1 #param_expr(RolaNameParam, Tasks.RoleName) #param_expr(TypeNameParam, Tasks.TypeCaption)

Представление отображает некоторую информацию о заданиях:

Теперь перейдем к рабочему месту пользователя и добавим эти представления как связанные:

  1. Сначала добавим представление ExampleCurrentTasksByType как обычное представление

  2. Добавим новое представление как дочернее к ExampleCurrentTasksByType

    Оно отображается как вложенное в ExampleCurrentTasksByType

  3. Выберем в его параметрах представление ExampleCurrentTasks

  4. Теперь мы видим, что наше представление по заданиям вложено в представление по заданиям по типам:

  5. Разделим область представления на две части по горизонтали

  6. И добавим в верхнюю часть области родительское представление ExampleCurrentTasksByType, а в нижнюю – ExampleCurrentTasks

  7. Для дочернего представления доступно связывание параметров представления с параметрами или данными родительского представления.

    Посмотрим как выглядят представления до связывания параметров

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

  8. Свяжем параметр имени роли дочернего и родительского представлений:

    Проверим что параметр “пробросился” - т.е. при указании параметра для родительского представления, дочернее представление также фильтруется по указанному значению параметра:

  9. Свяжем параметр имени типа задания дочернего представления с данными выбранной строки (колонкой имени типа) – при щелчке мышью по строке родительского представления, в параметр дочернего будет подставлено значение из выбранной колонки выбранной строки родительского

    Проверим:

Добавление параметра для полнотекстового поиска

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

Рассмотрим на примере представления Мои документы (MyDocuments):

  1. В Метаданные представления добавляем новый #param:

    #param(Alias: Content, Caption: Поиск по файлам, Hidden: false, Type: nvarchar, Multiple: false, AllowedOperands: Contains)

  2. В Запрос добавляем параметр nvarchar(4000) (потому что исходный параметр указан как nvarchar(max), и такое значение недопустимо для функции FREETEXT для полнотекстового поиска):

    #if(Content) { declare @ContentParam nvarchar(4000) = #param(Content) }

  3. В Запрос, в условие where добавляем полнотекстовый поиск по содержимому всех индексированных файлов в карточке (по пустым строкам искать нельзя, поэтому явно проверяем, что введённая строка в фильтре не пустая и не состоит только из пробелов):

    #if(Content && !string.IsNullOrWhiteSpace(Content.Value)) { and exists ( select 1 from Files f with(nolock) inner join FileVersions fv with(nolock) on fv.ID = f.RowID inner join [tessa-files].[dbo].[FileContent] ft with(nolock) on ft.VersionRowID = fv.RowID where f.ID = t.ID and freetext(ft.Content, @ContentParam) ) }

Note

В примере указано, что таблица FileContent расположена в базе данных с именем tessa-files на том же сервере баз данных. Если имя базы данных отличается, база данных расположена на связанном сервере (linked server) или таблица FileContent задана в текущей базе данных, то измените имя [tessa-files].[dbo].[FileContent] в соответствии с вашими требованиями. Рекомендации по настройке базы данных для полнотекстового индексирования приведены в Руководстве по установке

Сохраняем представление и проверяем: в представлении появился добавленный нами поисковый параметр, который будет выполнять поиск строки в приложенных к карточкам файлах:

Особенности работы с PostgreSQL

В данном разделе описаны особенности написания представлений при использовании СУБД PostgreSQL.

  • Имена таблиц и колонок, в которых есть заглавные буквы, нужно заключать в двойные кавычки. Например: "DocumentCommonInfo".

  • Аналог cross applylateral join.

  • PostgreSQL считает count(*) медленнее, чем MSSQL. Отказывайтесь от сабсета Count там, где данных много. Если представление используется и в MSSQL, и в PostgreSQL, Count можно отключить только для PostgreSQL, используя #if в метаинформации. Пример в представлении Documents.

  • При поиске по части строки с использованием TRIGRAM индексов (pg_tgrm) учитывайте, что длина входящей строки должна быть не меньше 3 символов, иначе PostgreSQL будет обходить весь индекс, что очень медленно, т.е. по факту индекс в таком случае работать не будет. В плане выполнения нет никакой разницы, кроме огромной стоимости.

    Например:

    Where Column like '%123%' – будет работать, а

    Where Column like '%12%' - будет выполняться очень медленно.

  • Рекомендуется использовать колонки с типом Int16, вместо типа Byte, т.к. в PostgreSQL они преобразуются в Int16, а в MSSQL остаются как Byte, что приводит к разному поведению при чтении из базы в коде. Актуально, если вы сейчас на MSSQL, но планируете мигрировать на PostgreSQL, или же вы пишете универсальный модуль, работающий с любой СУБД.

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

  • Пример индекса:

    b-tree индекс по полю без lower и без text_pattern_ops, в том числе в составных индексах.

    create index .. on ... using btree ( “Name” )

    Используется в следующих случаях:

    1. Когда нужен быстрый поиск на равенство с учетом регистра.

      Select * from "Table" where "Name" = 'something'

      В системе документооборота Tessa это обычно только штрих-код.

    2. Когда нужна сортировка по этому полю с учетом правил локали/коллейшена (т.е. сортировка с учетом разницы между буквами, цифрами, большими и маленькими буквами).

      select * from "Documents" order by "FullNumber"

    Важно быть уверенными, что индекс действительно используется системой и сортировка не выполняется в памяти системы. Если запрос написан некорректно (например, есть лишние условия выборки, из-за которых оптимизатор выбирает другой индекс), то польза от такого индекса только в 1-м случае (поиск по равенству).

    В системе документооборота Tessa это обычно номера документов, краткие имена контрагентов и сотрудников (ролей).

  • Пример индекса:

    b-tree индекс по полю c lower и без text_pattern_ops, в том числе в составных индексах.

    create index .. on ... using btree ( lower("Name") )

    Используется в следующих случаях:

    1. Когда нужен быстрый поиск на равенство без учета регистра.

      Например, в Tessa это логин пользователя. Система при логине ищет без учета регистра.

      Важно не забывать, что в запросе должно быть написано where lower("name") = 'name', иначе работать не будет.

    2. Когда нужна проверка на уникальность без учета регистра средствами базы. В этом случае индекс должен быть unique.

  • Пример индекса:

    Gin + pg_tgrm индекс по полю с использованием lower.

    create index .. on ... using gin ( lower("Name") gin_trgm_ops )

    Используется, когда нужен эффективный поиск с использованием паттернов.

    Select * from "Documents" where lower("Name") like '%sometext%'

    Важно не забывать, что в запросе должно быть написано where lower("name") like .., иначе работать не будет.

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

    В системе документооборота Tessa это обычно полный номер документа, имя контрагента, имя роли, тема документа.

    Иногда может быть нужен и b-tree, и gin индекс.

  • Пример индекса:

    Полнотекстовый индекс по колонке

    GIN индекс по to_tsvector(column)

    Используется, когда нужен быстрый и эффективный поиск по лексемам. Работает с учетом морфологии, но не позволяет искать по части слова – только по целым словами.

    Например, для поиска по части имени контрагента такой индекс не подойдет, для этих целей используются pg_tgrm индексы.

    select * from "SuperDocuments" sd, 'май \| первый' q where to_tsquery(sd.Subject) @@ q;

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

    Для того, чтобы сделать immutable функцию (только такие функции можно использовать в индексах), нужно в теле функции написать:

    CREATE FUNCTION make_tsvector(title TEXT, content TEXT) RETURNS tsvector AS $$ BEGIN RETURN (setweight(to_tsvector('english', title),'A') \|\| setweight(to_tsvector('english', content), 'B')); END $$ LANGUAGE 'plpgsql' IMMUTABLE;

    Более подробно:

  • Пример индекса:

    b-tree индекс по полю c lower и с text_pattern_ops, в том числе в составных индексах

    create index .. on … using btree ( lower(“Name”) text_pattern_ops )

    Warning

    В НАСТОЯЩИЙ МОМЕНТ НЕ ПОДДЕРЖИВАЕТСЯ!

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

    select * from "SuperDocuments" where TypeId = ... and Date = ... and Subject like '%sometext%'

    В этом случае, необходимо на примерах убедиться, что индекс действительно используется и что такое использование более эффективное, чем gin + pg_tgrm

  • Пример индекса:

    Gist + pg_tgrm индекс по полю с lower

    Рекомендуется использовать только в случае, если в этом действительно есть необходимость.

    Важные отличия индексов GIN и GiST:

    • GIN — быстро ищет, но не слишком быстро обновляется. Отлично работает, если вы сравнительно редко меняете данные, по которым ищите;

    • GiST — ищет медленнее GIN, зато очень быстро обновляется. Может лучше подходить для поиска по очень часто обновляемым данным.

Back to top