Структура базы данных Docsvision. От разработчика — разработчику!
Docsvision — это не просто программа, это платформа, позволяющая создавать свои решения для электронного документооборота. Статья нашего коллеги — разработчика Docsvision Димы Лейкина — предназначена как раз для разработчиков таких решений, к коим мы относим партнёров нашей компании и сотрудников IT-подразделений наших компаний-заказчиков.В материале, разделённом на 5 логических частей, — базовая информация о том, как устроена система Docsvision. Кроме того, для разработчиков, которые хотят устроиться к нам на работу, эти знания будут дополнительным плюсом.
В целом, Docsvision — это клиент-серверная система, и разработка своего решения сводится к разработке набора карточек (то есть библиотеки).
Карточка — базовое понятие в системе Docsvision. С точки зрения клиента, карточка — это тот UI, который он видит, когда работает с документом или заданием. С точки зрения программиста клиентской части, карточка — это объектная модель, которая позволяет сохранять информацию на сервер. С точки зрения программиста серверной части, карточка — это набор таблиц и хранимых процедур для доступа к ним. С точки зрения разработчика карточки, карточка — это, прежде всего, метаданные. По этим метаданным генерируются таблицы и хранимые процедуры карточки, в них содержатся атрибуты, отвечающие за безопасность, способ загрузки данных карточки на клиент и многие другие.
Справочник — другое важное понятие. Справочник — это карточка, которая существует в единственном экземпляре. Например, справочник сотрудников, справочник сохраненных поисковых запросов, справочник ролей и т.д. С самой системой поставляется несколько уже написанных библиотек карточек. Это библиотеки Platform, ManagedPlatform, Takeoffice, Workflow, Backoffice.Первая серия статей посвящена основе системы — базе данных Docsvision. Конечно, это далеко не полное описание. В основном информация посвящена принципам работы системы, поэтому многими подробностями пришлось пожертвовать ради простоты изложения.
Часть 1. Секционные таблицыПри разработке своей библиотеки карточек с помощью утилиты CardManager создаются xml с описаниями метаданных для карточек. Потом по этим метаданным генерируются таблицы и хранимые процедуры в базе, позволяющие работать с этими карточками. Метаданные — это по сути описание типа карточки.Вкратце о метаданных С точки зрения метаданных, карточка — это набор секций. Секцию можно представить себе, как таблицу в базе, так будет понятней. Секция — это набор полей. Поле можно себе представить, как колонку этой таблицы. Секция может быть коллекционной, деревянной или типа struct. Секция может иметь дочерние секции, об этом более подробно я пишу ниже.Каждая карточка имеет уникальный идентификатор — идентификатор типа карточки. Аналогично секции и поля имеют уникальные идентификаторы. Идентификаторы секций очень важны, поскольку в данный момент имена секционных таблиц генерируются следующим образом: [dbo].[dvtable_{id}], где id — это идентификатор секции.
Начинающие часто путаются с типами и экземплярами. Например, в таблице [dbo].[dvsys_instances], где хранится информация об экземплярах карточки, в поле InstanceID (идентификатор экземпляра карточки) начинают искать идентификатор типа карточки для карточки документа. И очень удивляются, когда его там не находят. (Тут надо заметить, что в последних версиях Docsvision для справочников идентификатор карточки равен идентификатору типа).
Секция с точки зрения хранения информации в БД С точки зрения хранения информации секция — это таблица БД. Полю секции в БД соответствует колонка таблицы. Кроме колонок для полей, в каждой «секционной» таблице есть так называемые системные колонки (RowID, InstanceID, ParentRowID, ParentTreeRowID и др.)Колонки InstanceID и RowIDRowID — уникальный идентификатор (Guid) строчки секционной таблицы (первичный ключ).InstanceID — идентификатор (Guid) экземляра карточки, к которой принадлежит данная строчка. Соответствует идентификатору карточки из таблицы [dbo].[dvsys_instances].
Можно представить себе секционную таблицу следующим образом:
InstanceID RowID … CardId1 RowId1 … CardId1 RowId2 … CardId2 RowId3 … CardId2 RowId4 … В одной и той же таблице хранится информация о строчках секции для всех экземпляров карточек этого типа.Представление секций разного типа в БДСекция может быть коллекционной, деревянной и типа struct. Название секций произошли, видимо, от тех структур данных, для хранения которых предназначены эти типы секций.
Коллекционные секцииКоллекционная секция предназначена для хранения коллекции объектов. С точки зрения БД это означает, что среди строк секционной таблицы могут быть строки с одним и тем же InstanceID.
Секции типа structСекция типа struct предназначена для хранения структуры данных. По сути, это та же коллекционная секция, но только в коллекции может быть максимум один объект. С точки зрения БД это означает, что в секционной таблице не может быть двух строк с одинаковым InstanceID. Колонки секционной таблицы соответствуют полям структуры.
Деревянные секцииДеревянная секция предназначена для хранения деревьев. Деревья хранятся следующим образом: есть системная колонка ParentTreeRowID, в которой для каждой строчки записывается идентификатор родительской строчки в дереве, либо Guid.Empty, если родительской строчки нет. В деревянной секции можно хранить несколько деревьев для одного экземпляра карточки, здесь никакого ограничения нет.При удалении строчки в дереве все ее дочерние строчки будут удалены, это обеспечивают хранимые процедуры удаления строк.
Дочерние секцииДеревянные секции позволяют хранить для объекта коллекцию дочерних объектов того же типа. А что, если мы хотим хранить коллекцию дочерних объектов другого типа? Для этого предназначены дочерние секции. «Классический» пример родительской и дочерней секции — это секции подразделений и сотрудников в справочнике сотрудников. С каждым подразделением связана коллекция сотрудников. В БД это выглядит следующим образом: в секционной таблице для сотрудников в системной колонке ParentRowID (не путать c ParentTreeRowID) указывается идентификатор родительского подразделения.
RowID CompanyID1 CompanyID2 RowID ParentRowID EmployeeID1 CompanyID1 EmployeeID2 CompanyID1 Для секционной таблицы дочерней секции генерируется внешний ключ на родительскую таблицу (с ParentRowID дочерней на RowID родительской таблицы) с каскадным удалением. То есть, например, при удалении подразделения, все его сотрудники будут удалены.Практикум Как можно использовать эти знания на практике? Допустим, у нас есть база Docsvision, и мы хотим посмотреть, какие сотрудники зарегистрированы в справочнике сотрудников. Для начала нам надо узнать идентификатор секции сотрудников справочника сотрудников. Проще всего, конечно, посмотреть в CardManager или в xml, но если их нет под рукой, не беда: Находим идентификатор справочника сотрудников:
select * from [dbo].[dvsys_carddefs] where Alias like '%Staff%' Получаем список его секций: select * from [dbo].[dvsys_sectiondefs] where CardTypeID = '6710B92A-E148–4363–8A6F-1AA0EB18936C' Видим в поле SectionTypeID для секции с алиасом Employees идентификатор секции сотрудников 'DBC8AE9D-C1D2–4D5E-978B-339D22B32482». Делаем запрос из секционной таблицы сотрудников: select * from [dbo].[dvtable_{DBC8AE9D-C1D2–4D5E-978B-339D22B32482}] Список сотрудников перед нами. Теперь, допустим, мы хотим посмотреть, какие сотрудники есть в подразделении с именем Test. Аналогично узнаем идентификатор секции подразделений и пишем: select Emp.* from [dvtable_{DBC8AE9D-C1D2–4D5E-978B-339D22B32482}] Emp join [dvtable_{7473F07F-11ED-4762–9F1E-7FF10808DDD1}] Company ON Emp.ParentRowID = Company.RowID where Company.Name = 'Test' Здесь мы использовали то, что секция сотрудников — дочерняя по отношению к секции подразделений. Теперь попробуем вывести подразделение Test и все его дочерние (в дереве) подразделения: ; WITH Companies (RowID, ParentTreeRowID, Name) AS ( SELECT Company.RowID, Company.ParentTreeRowID, Company.Name FROM [dvtable_{7473F07F-11ED-4762–9F1E-7FF10808DDD1}] Company WHERE Company.Name = 'Test' UNION ALL SELECT Company.RowID, Company.ParentTreeRowID, Company.Name FROM [dvtable_{7473F07F-11ED-4762–9F1E-7FF10808DDD1}] Company JOIN Companies ON Company.ParentTreeRowID = Companies.RowID ) SELECT RowID, ParentTreeRowID, Name FROM Companies Здесь мы использовали то, что секция подразделений является деревянной секцией. Часть 2. Немного подробнее о метаданных Xml c метаданными карточек представляет из себя xml файл с метаданными карточки VersionedFile библиотеки Platform:
Метаданные — это, по сути, определение типа для экземпляров карточки. Очень часто карточкой называют и метаданные, и экземпляры карточек в базе, что несколько сбивает с толку. Когда мы говорим о метаданных, под карточкой всегда будет пониматься тип карточки, под идентификатором карточки — идентификатор типа карточки и т.д. В метаданных задаются имя и идентификатор карточки, ее атрибуты, и дерево секций. Для каждой секции задается имя и идентификатор секции, ее атрибуты и поля. Для каждого поля задается имя и идентификатор поля, а также его тип.Среди атрибутов карточки стоит, прежде всего, отметить атрибут, отвечающий за то, является ли карточка справочником. Среди атрибутов секции наиболее важный атрибут — это тип секции (коллекционная, деревянная или типа struct).Дочерние секции задаются в xml файле следующим образом. В элементе есть дочерний элемент , где указываются элементы для дочерних секций. Дочерняя секция в свою очередь также может иметь дочерние секции. Таким образом, получается дерево секций.
Утилита CardManager Для работы с метаданными в Docsvision используется утилита для разработчиков CardManager. Утилита позволяет автоматизировать создание и редактирование метаданных карточек.На скриншоте утилита CardManager с открытой библиотекой Backoffice.
На скриншоте — метаданные справочника сотрудников библиотеки Backoffice. Открыта деревянная секция AlternativeHierarchy, предназначенная для хранения групп пользователей. В ней видны дочерние секции Group и GrpViewFields, а также поля Name, Comments, AccountName и другие.
Часть 3. Ссылки Поля могут быть разных типов, и в том числе — ссылки на строки (refid) и ссылки на экземпляры карточек (refcardid).
RowID MyReference RowID Id1 RowID … Id1 … В случае ссылки на строку в ячейке таблицы прописывается идентификатор строки, на которую ссылается данная строка. Но в какой таблице искать строку по этому идентификатору? Это определяется по метаданным того поля, которое предназначено для хранения ссылки (в случае на рисунке — по метаданным поля MyReference).При хранении ссылки на карточку в ячейке таблицы прописывается идентификатор экземпляра карточки InstanceID из таблицы [dbo].[dvsys_instances]. В метаданных указано, на карточку какого типа хранится ссылка.RowID MyCardReference RowID Id1 InstanceID CardTypeID … Id1 TypeId … Типы ссылок Hard, Weak, Auto Ссылка на карточку может иметь тип Hard, Weak, Auto.Hard, или жесткая ссылка, означает следующее: когда удаляется последняя жесткая ссылка на карточку, то карточка будет удалена.Weak, или слабая ссылка, никак не влияет на удаление карточки.Auto — автоматическая ссылка также не влияет на удаление карточки, но при удалении карточки сама ссылка будет обнулена.Таблица [dbo].[dvsys_links] Для хранения ссылок используется таблица [dbo].[dvsys_links]. В этой таблице собрана вместе та информация о ссылках, которая разбросана по разным секционным таблицам. Рассинхронизации тут произойти не может, поскольку добавление/удаление записей в таблицу обеспечивают те же хранимые процедуры, которые работают с секционными полями.Таблица имеет следующие полезные колонки:
RowID — строчка которая ссылается FieldID — идентификатор типа поля, предназначенного для хранения значения ссылки SourceCardID — идентификатор экземпляра карточки, которая ссылается DestinationTypeID — идентификатор экземпляра карточки, на которую ссылаемся Type — тип ссылки Для чего нужны ссылки Ссылки делают карточку компонентом повторного использования. Например, если Вы хотите, чтобы создаваемая Вами карточка содержала коллекцию файлов, поддерживающих версионинг, то достаточно сделать поле со ссылкой на карточку FileList системной библиотеки Backoffice.Часть 4. Системные таблицы и библиотеки Основные системные таблицы [dbo].[dvsys_globalinfo] — таблица содержит информацию о версии базы данных Docsvision. В этой таблице есть полезное поле Version, содержащее текущую версию базы данных.Иногда требуется обновить версии для всех библиотек, чтобы они соответствовали версии базы данных, а погружать в базу новые версии библиотек не хочется. Для этого существует следующий способ: посмотреть версию в [dbo].[dvsys_globalinfo] и далее вызвать хранимую процедуру: exec [dbo].[dvsys_help_set_version] @Version = номер версии Таблицы метаданных:[dbo].[dvsys_libraries] — информация об установленных библиотеках карточек [dbo].[dvsys_carddefs] — информация о установленных карточках [dbo].[dvsys_sectiondefs] — информация о секциях [dbo].[dvsys_fielddefs] — информация о полях Другие важные таблицы[dbo].[dvsys_instances] таблица экземпляров карточек [dbo].[dvsys_links] таблица ссылок [dbo].[dvsys_locks] таблица блокировок [dbo].[dvsys_security] безопасность [dbo].[dvsys_crypto] криптообъекты [dbo].[dvsys_log], [dbo].[dvsys_log_application], [dbo].[dvsys_log_security] таблицы логов Для хранения файлов важными являются 2 таблицы:[dbo].dvsys_files [dbo].dvsys_binaries В таблице dvsys_files хранится информация о файле, а в dvsys_binaries — его содержимое.Также важно упомянуть, что для полей карточек есть тип fileId. В поле такого типа будет храниться идентификатор файла из таблицы dvsys_files.Библиотека Platform Справочник папокЕсли Вы когда-нибудь открывали Docsvision Navigator, то первое что Вы видели — это дерево папок и грид с представлением содержащихся в папке карточек. На самом деле, в папке содержатся не сами карточки, а ярлыки на них.Справочник папок — это справочник FoldersCard библиотеки Platform, где хранится информация о папках, а также о находящихся в папках ярлыках на карточки. Папки хранятся в деревянной секции Folders.
Ярлыки хранятся в дочерней по отношению к ней секции Shortcuts. В этой секции есть поля HardCardID и CardID. И то, и другое поле является ссылкой на карточку. Разница только в том, что поле HardCardID представляет собой жесткую ссылку, а CardID ссылку типа Auto. Таким образом, ярлыки на одну и ту же карточку могут находится в разных папках, и карточка будет удалена, когда будет удален последний ярлык с жесткой ссылкой на неё.
Папки могут быть следующих типов: обычные папки, виртуальные папки, папки-делегаты и системные папки. Виртуальные папки отличаются тем, что для возвращения информации о находящихся в них ярлыках используется поисковый запрос. Папки-делегаты — это, по сути, указатели на другие папки. Системные папки — обычно какие-то особенные папки, например, папка результатов поиска или папка «корзина». Многие системные папки невидимы для пользователя. Посмотреть системные папки можно так:
select Name from [dbo].[dvtable_{FE27631D-EEEA-4E2E-A04C-D4351282FB55}] Folders where [Type] = 16 — System Карточка файла с версиямиВ библиотеке Platform есть карточка VersionedFile. Эту карточку удобно использовать в случаях, когда необходимо хранить несколько версий одного файла. В карточке есть деревянная секция Versions, которое позволяет хранить дерево версий файла. В секции Versions есть поле FileID типа fileID, где хранится ссылка на файл. В секции MainInfo есть полезное поле CurrentID (типа refid), содержащее ссылку на версию, которая считается текущей.Карточка нумератораКарточка нумератора используется для выдачи номеров документам и другим карточкам.
Карточка сохраненных представленийКарточка используется для хранения информации о пользовательских представлениях — тех представлениях, которые показывает грид Docsvision Navigator.
Карточка сохраненных поисковых запросовКарточка используется для хранения информации о пользовательских поисковых запросах.
Библиотека Backoffice Справочник видовТак уж получилось, что создатели библиотеки Backoffice посчитали недостаточным разделение карточек на типы, и решили в рамках библиотеки уточнить тип карточки, добавив к нему вид. Скажем, есть документ, а есть входящий документ — это вид документа. Вид может иметь дочерние виды, которые наследуют какие-то его особенности, а какие-то имеют свои. Так и получилось дерево видов. Все эти виды хранятся в справочнике видов.Карточки документа, задания, группы заданий, согласованияЭто основные рабочие карточки системы Docsvision. Они соответствуют основным сущностям документооборота. Они предоставляют большое количество различных сервисов и участвуют в различных сценариях работы.
Справочник состоянийВ справочнике состояний хранится конечный автомат состояний карточек. При переходе из состояния в состояние производятся операции.
Справочник ролейВ справочнике ролей хранится информация о ролевой безопасности. По сути, это трехмерная матрица роли — состояния — доступные операции. UI справочника позволяет увидеть сечения этой трехмерной матрицы.
Справочник разметокВ справочнике разметок можно настроить внешний вид карточки, добавить к ней элементы управления, подписаться на их события, определить динамические поля и связать их с элементами управления.
Справочник скриптовСправочник скриптов позволяет написать для карточек свои скрипты.
Справочник сотрудниковВ справочнике сотрудников хранятся сотрудники и подразделения, а также группы и роли.
Справочник контрагентовВ справочнике контрагентов хранятся организации и сотрудники — контрагенты.
Часть 5. Подсистема поиска и представлений Поиск и представления не сильно отличаются друг от друга. Основная идея: с помощью UI или с помощью кода создать описание поиска/представления. По этому описанию сервер сгенерирует хранимую процедуру, которая будет возвращать выборку с результатами. Описание поиска хранится в справочнике поисковых запросов, а представления — в справочнике сохраненных представлений. Поиск/представление должны иметь идентификатор, для того чтобы при повторном вызове вызывалась уже существующая хранимая процедура, что влияет на производительность.Представление — это то, что пользователь видит в гриде навигатора. Во-первых, есть системное представление (дайджест), которое используется по умолчанию. Также различаются обычные представления и представления с постраничным выводом информации (так называемые keyset представления). Представление в качестве своего источника данных может использовать обычную папку, или результаты поиска, или что-то еще. Поиск и представления поддерживают параметры, которые задаются в момент вызова либо пользователем через UI, либо программно. В качестве параметров могут быть заданы поисковые слова (Я, Сегодня, Мои заместители и многие другие). Допускаются коллекционные параметры.
Определение поиска и представлений представляет собой, по сути, небольшой язык с синтаксисом xml, и этот язык поддерживает много различных возможностей. Система генерации хранимых процедур на сервере — это транслятор из Xml в Sql.
Поисковая подсистема поддерживает 2 типа поиска — атрибутивный и полнотекстовый. Полнотекстовый поиск позволяет искать карточки или файлы, содержащие определенную строку. Атрибутивный поиск позволяет накладывать условия на значения полей выводимых карточек. Условие на значение поля может использовать операции равно, не равно, больше, меньше, и другие, в зависимости от типа поля. Условия могут комбинироваться с помощью И или ИЛИ. Таким образом, получается дерево условий. При генерации хранимой процедуры по ним генерируется условия в WHERE.
В представлениях используется аналогичный подход, поскольку они тоже в общем случае должны выводить не все карточки. В отличие от поиска, представления поддерживают так называемые вычисляемые поля. Генерацию вычисляемого поля можно представить себе, как добавление еще одного выражения в список инструкции SELECT результирующей выборки хранимой процедуры. Выражения для вычисляемого поля напоминают выражения для дерева условий. По сути, это то же дерево, только вместо операций сравнения в нем наиболее часто используются арифметические операции, а также выражения CASE WHEN (напоминающие switch в C#).
Для простоты схему выборки в представлениях можно представить следующим образом:
SELECT обычные поля Вычисляемые поля FROM основная таблица JOIN список присоединенных таблиц WHERE условия На самом деле, конечно, все сложнее. Во-первых, представление может содержать не одну такую выборку, а несколько, и эти выборки объединяются с помощью UNION ALL. Это может быть полезно, чтобы вывести в одно представление карточки разных типов. Во-вторых, в постраничных представлениях чтение всех данных для всех страниц из базы привело бы к существенному снижению производительности, поэтому в генераторе используется соответствующая магия по одной выборке генерируются несколько хранимых процедур.Представления также предоставляют расширенные возможности, включающие использование агрегаций, конкатенаций, раскрытие деревянных секций. Поддерживается сортировка для представлений. А группировка уже делается на клиенте.
Более подробно модель поиска и представлений можно рассмотреть в одной из будущих статей, а в этом блоке изложена базовая информация.