Поиск

Возможности поисковых механизмов, предлагаемых базовым API и объектной моделью, значительно отличаются. Базовый API предлагает больше функциональности в части формирования, а также использования поисковых запросов, а объектная модель — простой и функциональный механизм поиска.

Поиск объектов с использованием базового API

Возможности поиска объектов методами базового API можно разделить на две принципиально разные группы:

Серверный поиск

Наиболее мощный, выполняющийся на сервере метод. Клиент подготавливает XML-строку, содержащую поисковый запрос в специальном формате, и передает её серверу Docsvision, который должен вернуть результат в виде строк, помещаемых затем в кэш.

Поисковый запрос может быть передан в виде готовой XML-строки, либо в виде объекта, сформированного методами ObjectManager.

Клиентская фильтрация

Простая фильтрации строк XML-документа карточки с использованием XPath-выражений, предлагаемая ObjectManager.

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

Серверный поиск

Поисковый запрос для выполнения серверного поиска описывается строкой в формате XML. Он строится по специальной схеме (Search.xsd), которая описывает все возможные комбинации условий поиска.

Конкретный XML поискового запроса может быть получен, например при помощи визуального редактора (диалог Расширенный поиск в Windows-клиенте), где пользователь может оперировать семантически значимыми понятиями, такими как тип карточки — секция — поле — значение. Но результатом работы этого визуального редактора все равно в конечном счете станет XML-строка запроса, которая и будет передана на сервер для выполнения. Этот XML-текст может быть скопирован из диалога и использован в коде для выполнения аналогичных запросов.

Также текст поискового запроса может быть задан программно — например при помощи набора стандартных объектов для работы с XML (XML Parser). Однако, для упрощения программного создания и редактирования запросов, в объектной модели ObjectManager предусмотрен ряд специализированных объектов.

Структура запроса
Рисунок 1. Структура запроса

Все объекты для работы с поиском расположены в выделенном пространстве имён DocsVision.Platform.ObjectManager.SearchModel, поэтому для их использования нужно подключить данное пространство имён к проекту.

Базовым объектом для построения поискового запроса является объект SearchQuery. Для создания этого объекта, предназначен специальный метод CreateSearchQuery пользовательской сессии (UserSession).

Сам запрос состоит из двух частей — атрибутивного поиска (AttributiveSearch) и полнотекстового запроса (FullTextSearch). Эти части независимы друг от друга и могут фигурировать в запросе как по отдельности, так и одновременно. Возможно объединение результатов этих частей по И/ИЛИ, для этого предназначен флаг CombineResults.

Количество карточек в результатах поиска можно ограничить свойством Limit.

Пространство карточек, на котором исполняется запрос, ограничивается объектом Scope. Это ограничение накладывается и на атрибутивную, и на полнотекстовую части запроса и может включать в себя ограничение по конкретным типам карточек (CardTypes) и по папкам, в которых расположены ярлыки карточек (Folders).

SearchQuery query = session.CreateSearchQuery();


query.Scope.CardTypes.AddNew(new Guid("C1FED883-08DE-420F-8FB4-C16CEFFC1630"));
query.Scope.CardTypes.AddNew(new Guid("FA0C389E-1095-4BC1-BEDC-793463742571")); (1)

query.Scope.Folders.AddNew(new Guid("C0BCEB41-A19B-4813-A355-82EB6FD4F5F0")).IncludeSubfolders = true; (2)
1 Установка ограничения по типам карточек.
2 Установка ограничения по папкам.

Отменить наложенные ограничения может свойствo AllCards — при его установке поиск производится по всем карточкам, независимо от указанных ограничений.

Свойство IncludeLinkedCard позволяет включить в результаты поиска карточки, связанные с уже найденными по ссылкам на определённую глубину вложенности (глубина задаётся в качестве значения свойства). Дополнительно можно ограничить тип ссылок (LinkTypes) и их направление (LinkDirections).

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

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

Чтобы получить сформированную строку запроса в формате XML надо воспользоваться методом GetXml(). В этом методе параметр ForSearch=true означает, что строка формируется с целью отправки на сервер, и не должна содержать неактивные настройки (например, такие, как поисковые слова).

Значение ForSearch=false означает, что строка получается для сохранения где-либо и поэтому в неё включаются все настройки поиска. Второй параметр метода — Parameters — содержит массив значений, которые будут подставлены на место параметров, заданных в поиске.

Существует также обратный метод — ParseXml(stringqueryXml). С его помощью можно проинициализировать объект SearchQuery из сохранённой строки XML-запроса.

Полнотекстовая часть поискового запроса (FullTextSearch) очень проста и позволяет задать только саму строку для поиска (QueryString) и режим поиска (Mode), чтобы ограничить поиск только файлами или всеми карточками. Поиск возможен только по карточкам, для которых в схеме разрешён поиск. В результате поиска по файлам будут возвращены карточки-владельцы найденных файлов, а не сами файлы.

Пример кода построения полнотекстового запроса:
SearchQuery query = session.CreateSearchQuery();

query.FullTextSearch.QueryString = "Договор на обслуживание";
query.FullTextSearch.Mode = FullTextSearchMode.Files; (1)
1 Условия полнотекстового поиска

Атрибутивная часть описывает условия на значения полей карточек, которые требуется найти. Она состоит из запросов к карточкам (CardTypeQuery). Если требуется найти все карточки, следует выставить в true параметр AllCards. Каждый запрос к типу карточки состоит из набора запросов к секциям карточки (SectionQuery).

Структура атрибутивного поиска
Рисунок 2. Структура атрибутивного поиска

Каждый запрос к секции состоит из набора условий (Condition), накладываемых на конкретные поля секции. Условия организуются в иерархические группы (ConditionGroup). Группа условий — это аналог скобок в выражении. Условия внутри ConditionGroup объединяются операцией (И/ИЛИ), задаваемой свойством Operation. Группы ConditionGroup могут быть вложенными.

Структура поиска по секции
Рисунок 3. Структура поиска по секции

Сформированный поисковый запрос можно выполнить, вызвав метод CardManager.FindCards (в параметре метода передается XML-строка сформированного запроса). Метод возвращает CardDataCollection — коллекцию найденных карточек, соответствующих условиям запроса. Если же в результате выполнения запроса не было найдено ни одной карточки, коллекция будет пуста.

В качестве примера рассмотрим построение запроса, который вернёт все карточки типа "Документ", с названием Sample. Перед тем как строить запрос, необходимо выяснить идентификаторы типа карточки, который собираемся искать. Также необходимо узнать идентификаторы секций, в полях которых производится поиск. Целесообразно оформить эти идентификаторы в виде констант:

const string ID_CARDTYPE = "{B9F7BFD7-7429-455E-A3F1-94FFB569C794}";
const string ID_SECTION = "{30EB9B87-822B-4753-9A50-A1825DCA1B74}";

SearchQuery searchQuery = userSession.CreateSearchQuery();

CardTypeQuery typeQuery = searchQuery.AttributiveSearch.CardTypeQueries.AddNew(new Guid(ID_CARDTYPE)); (1)

SectionQuery sectionQuery = typeQuery.SectionQueries.AddNew(new Guid(ID_SECTION)); (2)

sectionQuery.ConditionGroup.Conditions.AddNew("Name", FieldType.Unistring, ConditionOperation.Equals, "Sample"); (3)

string query = searchQuery.GetXml(); (4)

CardDataCollection coll = userSession.CardManager.FindCards(query); (5)
1 Поиск по типу карточки.
2 Поиск по секции.
3 Поиск по значению поля.
4 Получение текста запроса.
5 Выполнение запроса.

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

<Search Version="4300" CombineResults="OR">
 <AttributiveSearch>
  <CardTypeQuery CardTypeID="{B9F7BFD7-7429-455E-A3F1-94FFB569C794}">
   <SectionQuery Version="4300" SectionTypeID="{30EB9B87-822B-4753-9A50-A1825DCA1B74}">
    <ConditionGroup Alias="alias0" Operation="OR">
     <Condition Alias="alias1">
      <Field FieldType="unistring">Name</Field>
      <Op>EQ</Op>
      <Value>'Sample'</Value>
     </Condition>
    </ConditionGroup>
    <Options Limit="-1" />
   </SectionQuery>
  </CardTypeQuery>
 </AttributiveSearch>
 <Scope />
 <FulltextSearch Mode="CardsAndFiles">
  <QueryString />
 </FulltextSearch>
</Search>

При построении условий к секциям можно также использовать присоединенные секции (JoinSections). Такая необходимость может возникнуть, если поле, по которому нужно искать, физически содержится в секции другой карточки. В этом случае к запросу по секции (SectionQuery) нужно присоединить секцию связанной карточки (JoinSections.AddNew). Для присоединённой секции (JoinSection) необходимо указать псевдоним (Alias), поле секции, по которому идёт соединение (SectionField), а также идентификатор (ID) или псевдоним (JoinWith) присоединяемой секции или системной таблицы (TableName) и имя её поля (WithField). А в самом условии (Condition) необходимо, помимо имени поля, задать также псевдоним секции (SectionAlias), которому это поле принадлежит.

Пример: основная секция карточки, по которой строится запрос, содержит поле Регистратор — представляющее собой ссылку (REF_ID) на запись о сотруднике (из справочника сотрудников) и нужно найти все карточки, у которых E-Mail регистратора содержит подстроку "mail.ru". Такие детали, как E-Mail сотрудника, хранятся только в соответствующей секции справочника сотрудников — поэтому её необходимо присоединить при построении такого запроса:

const string REFSTAFF_EMPLOYEES = "{DBC8AE9D-C1D2-4D5E-978B-339D22B32482}"; (1)

JoinSection join = sectionQuery.JoinSections.AddNew("RegisteredBy_Info"); (2)
join.Id = new Guid (REFSTAFF_EMPLOYEES);
join.SectionField = "RowID";
join.WithField = "RegisteredBy";

Condition condition = sectionQuery.ConditionGroup.Conditions.AddNew("Email", FieldType.Unistring, ConditionOperation.EndsWith, "mail.ru"); (3)
condition.SectionAlias = "RegisteredBy_Info";
1 Идентификатор секции справочника сотрудников.
2 Присоединение секции справочника сотрудников.
3 Добавление условия на поле присоединенной секции.
Таким образом, при построении запросов можно использовать следующий алгоритм:
  1. Создать новый объект запроса (SectionQuery).

  2. При необходимости установить ограничения области поиска (Scope) на типы карточек (CardTypes) и папки (Folders).

  3. Если запрос полнотекстовый, задать строку поиска (FullTextSearch.QueryString).

  4. Если запрос содержит атрибутивные условия:

    1. Определить тип карточки, которая должна являться результатом поиска (CardTypeQuery).

    2. Определить, по какой секции карточки строится запрос (SectionQuery). Если поле, по которому производится поиск, физически находится в секции другой (связанной) карточки, то присоединить её (JoinSections).

    3. Сформировать как минимум одну группу условий (ConditionGroup) и определить операцию для условий (Operation).

    4. Добавить в группу условия (Condition), для каждого из которых определить следующее:

      • Имя поля, по которому производится сравнение (FieldAlias) и его тип (FieldType), а если поле принадлежит связанной секции — то имя этой секции (SectionAlias).

      • Условие (Operation).

      • Значение для сравнения (Value).

      Если условие использует агрегацию:
      • Задать имя функции (AggregateFunctionName).

      • При необходимости задать дополнительные условия на агрегируемые значения (AggregateConditionGroup).

  5. Выполнить запрос (CardManager.FindCards).

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

Текст запроса для поиска строк соответствует аналогичному запросу для поиска карточек, но в сокращённом варианте. Он начинается сразу с уровня объекта SectionQuery, который также является создаваемым при помощи соответствующего метода объекта UserSession:

SectionQuery secQuery = session.CreateSectionQuery();

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

При построении поисковых запросов внутри секции можно пользоваться всеми возможностями поисковой модели — присоединенными секциями, агрегациями, и т.д. Для этого используются те же самые объекты.

В приведенном далее примере выполняется поиск всех строк в справочнике сотрудников с фамилией Иванов:

const string REFSTAFF_CARDTYPE = "6710B92A-E148-4363-8A6F-1AA0EB18936C";
const string REFSTAFF_EMPLOYEES = "DBC8AE9D-C1D2-4D5E-978B-339D22B32482";

CardData staffData = session.CardManager.GetDictionaryData(new Guid(REFSTAFF_CARDTYPE)); (1)

SectionData section = staffData.Sections[new Guid(REFSTAFF_EMPLOYEES)]; (2)

SectionQuery query = session.CreateSectionQuery(); (3)

query.ConditionGroup.Conditions.AddNew("LastName", FieldType.Unistring, ConditionOperation.Equals, "Иванов"); (4)

RowDataCollection results = section.FindRows(query.GetXml()); (5)
1 Получение данных справочника.
2 Получение секции сотрудников.
3 Создание поискового запроса по секции.
4 Условие по полю фамилия.
5 Выполнение запроса.

Клиентская фильтрация

Клиентская фильтрация позволяет осуществить выборку строк с использованием XPath-выражений. Её имеет смысл применять в тех случаях, когда данные уже загружены на клиента и выполнять серверный поиск нет необходимости.

Фильтрация поддерживается для данных секций (SectionData и SubSectionData), а также для произвольных коллекций строк (RowDataCollection).

Выражения для поиска строятся стандартным для XPath способом (описание синтаксиса XPath-запросов можно найти в оригинальном стандарте, в MSDN или в специализированных изданиях).

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

Полный перечень этих полей для стандартных типов карточек можно посмотреть:
  • При помощи утилиты "CardManager", входящей в Resource Kit, открыв схему соответствующей карточки (схемы всех базовых типов карточек входят в состав пакета разработчика);

  • В разделе Описание полей стандартных карточек.

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

Клиентская фильтрация возвращает только один результат (строку). Это всегда первая по порядку строка, удовлетворяющая условиям поиска (если таким условиям удовлетворяет несколько строк).

Пример поиска сотрудника в справочнике сотрудников по фамилии:
const string ID_CARDTYPE = "{6710B92A-E148-4363-8A6F-1AA0EB18936C}";
const string ID_SECTION  = "{DBC8AE9D-C1D2-4D5E-978B-339D22B32482}";

string name = "Иванов"; (1)

CardData card = session.CardManager.GetDictionaryData(new Guid(ID_CARDTYPE));
SectionData section = card.Sections[new Guid(ID_SECTION)];
RowData row = section.FindRow("@SurName=\"" + strName + "\""); (2)
1 Фамилия сотрудника.
2 Клиентская фильтрация.

Для поиска строк в коллекции (RowDataCollection) параметры и способ использования метода идентичны соответствующему методу для секций. Единственное отличие заключается в том, что метод Filter может возвращать более чем один результат (RowDataCollection).

Сохранение поискового запроса

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

сохранённый запрос
Рисунок 4. сохранённый запрос

Для хранения сформированных запросов в системе предусмотрена специальная карточка — карточка сохранённых поисковых запросов (SavedSearchCard), расположенная в системной библиотеке карточек (DocsVision.Platform.ObjectManager.SystemCards). Чтобы избежать необходимости оперировать с данными этой карточки напрямую, в объектной модели реализован соответствующий объект — SearchCard. Данная карточка является справочником, и поэтому может быть получена по идентификатору типа:

const string SEARCH_CARD_TYPE = "{05E4BE46-6304-42A7-A780-FD07F7541AF0}";
SearchCard searchCard = session.CardManager.GetDictionary(new Guid(SEARCH_CARD_TYPE)) as SearchCard;
Пример создания нового поискового запроса и сохранения его в карточке:
SearchQuery query = session.CreateSearchQuery(); (1)

const string SEARCH_CARD_TYPE = "{05E4BE46-6304-42A7-A780-FD07F7541AF0}";
SearchCard searchCard = (SearchCard)session.CardManager.GetDictionary(new Guid(SEARCH_CARD_TYPE)); (2)

searchCard.Queries.AddNew("Мой новый запрос").Import(oQuery); (3)
1 Формирование поискового запроса.
2 Получение карточки сохранённых запросов.
3 Сохранение запроса в карточку.

Возможности поиска в контексте объектов

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

BaseCard foundCard = objectContext.FindObject<BaseCard>(query); (1) (2) (3)
1 objectContext — контекст объектов.
2 BaseCard — тип возвращаемого объекта. В качестве типа возвращаемого объекта может быть передан тип объектной модели карточки или строки.
3 query — поисковый запрос типа QueryObject.

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

QueryObject query = new QueryObject(StaffEmployee.AccountNameProperty.Name, "MYDOMAIN\\KukoffSA"); (1)

StaffEmployee employee = objectContext.FindObject<StaffEmployee>(query); (2)
1 Формируем поисковый запрос по полю AccountName.

StaffEmployee.AccountNameProperty.Name — возвращает название поля, по которому выполняется поиск.

"MYDOMAIN\\KukoffSA" — искомое значение.

2 Получаем сотрудника с искомой учетной записью.

StaffEmployee — класс (объектной модели) искомого объекта.

Также возможны более сложные поисковые запросы, которые включают несколько критериев поиска. К примеру, далее приведён код запроса, выполняющего поиск контрагента по названию и типу (организация или подразделение).

QueryObject query = new QueryObject();

query.AddCriterion(PartnersCompany.NameProperty.Name, "ООО Берег"); (1)

query.AddCriterion(PartnersCompany.TypeProperty.Name, (int)PartnersCompanyType.Organization, CompareOperation.Equals); (2)

query.Operation = QueryOperation.And; (3)

PartnersCompany partnersCompany = objectContext.FindObject<PartnersCompany>(query); (4)
1 Добавляем критерий поиска по названию контрагента.
2 Ограничиваем поиск только среди организаций.
3 Определяем логическую операцию И, применяемую к результатам поиска.
4 Получаем контрагента, соответствующего критериям.

Приведенные выше примеры используют метод FindObject, который предназначен для возврата единственного результата. Если в результате поиска будет найдено более одного объекта, будет возвращена ошибка. Данный метод следует использовать только в случае, когда точно известно, что будет возвращен максимум один объект. Если ожидается возвращение нескольких объектов, используйте метод FindObjects, который возвращает коллекцию объектов. Метод вызывается аналогично первому.