Поиск объектов в сценарии

Примечание
Перед началом работы со сценариями рекомендуется ознакомиться со стандартами написания программного кода на языке C#.

Наиболее частая операция в сценариях – это поиск каких-либо объектов в системе по различным условиям. В системе ELMA поиск осуществляется с помощью методов Find или FindAll менеджера объекта.

Использование фильтров

Наиболее эффективный метод поиска – это использование фильтров. Фильтры настраиваются при создании или редактировании объекта в Дизайнере в расширенном режиме на вкладке Дополнительные (рис. 1):

Рис. 1. Карточка объекта. Вкладка "Дополнительные". Блок "Настройки фильтра"

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

Рис. 2. Диалоговое окно настройки свойства объекта. Вкладка "Дополнительно"

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

Пример без использования Public API

Если фильтр создается без использования PublicAPI, то рекомендуется использовать метод InterfaceActivator. Для поиска применяется менеджер объектов EntityManager. Класс FetchOptions задает параметры поиска.

Пространства имен:

using EleWise.ELMA.Model.Services;
using EleWise.ELMA.Model.Managers;
using EleWise.ELMA.Model.Common;

Текст сценария:

//Получаем менеджер для объекта Питомец
var manager = EntityManager<Pitomec>.Instance;

//Создаем фильтр для объекта Питомец
var filter = InterfaceActivator.Create<PitomecFilter>();
var vozrastRange = new EleWise.ELMA.Model.Ranges.Int64Range();
            vozrastRange.From = 5;
            filter.Vozrast = vozrastRange;
            filter.Vid = new DropDownItem("Кошка");
 //Получаем результирующий список объектов по фильтру
var pets = manager.Find(filter , FetchOptions.All);

Пример с использованием Public API

Примечание
Актуальная документация по PublicAPI доступна по ссылке.

Тот же поиск по фильтру можно осуществить при помощи PublicAPI. При этом нет необходимости использовать менеджер объекта:

Пространство имен:

using EleWise.ELMA.API;

Текст сценария:

//Создаем фильтр для объекта Питомец
var vozrastRange = new EleWise.ELMA.Model.Ranges.Int64Range();
            vozrastRange.From = 5;
            var filter = PublicAPI.Objects.UserObjects.UserPitomec.Filter().Vozrast(vozrastRange).Vid(new DropDownItem("Кошка")).Filter;
 //Получаем результирующий список объектов по фильтру
var pets = PublicAPI.Objects.UserObjects.UserPitomec.Find(filter, FetchOptions.All);

Фильтр представляет собой специальный класс, который располагается в том же пространстве имен, что и класс объекта, и имя его получается из имени класса объекта с постфиксом Filter (т.е. для класса Pitomec у нас создается класс фильтра PitomecFilter).

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

Для системных объектов нельзя изменить настройки фильтрации переменных.

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

Поиск по явному совпадению с использованием LINQ

В языке C# 3.0 и выше существуют очень полезные конструкции, называемые "лямбда-выражения", которые можно использовать для поиска объектов. Параметр FetchOptions при этом использовать не обязательно, но можно.

var pets = EntityManager<Pitomec>.Instance.Find(p => p.Hozyain == context.Kontakt && p.Imya == "Шарик");

Обратите внимание, что в текущей реализации метода Find в PublicAPI применение поиска по лямбда-выражениям пока не реализовано.

В примере выше мы используем выражение p => p.Hozyain == context.Kontakt && p.Imya == "Шарик" для фильтрации. Согласно этому выражению, мы должны найти все объекты типа Питомец у которых параметр Хозяин равен значению переменной Контакт из контекста И Имя равно (полностью совпадает с) "Шарик".

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

При поиске с использованием лямбда выражений есть ряд ограничений на само выражение:
  • выражение может содержать только логические операторы И (&&) или ИЛИ (||) в любом сочетании;
  • выражение может искать только по равенству переменной и значения (==). При использовании данного выражения рекомендуется использовать ID объекта;
  • в выражении нельзя искать по вложенным полям для объектов;
  • в выражении нельзя использовать функции применимо к левому операнду - переменной.

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

Правильные выражения
EntityManager<Pitomec>.Instance.Find(p => p.Hozyain == context.Kontakt && p.Imya == pName) //Тут pName - это локальная переменная типа String.
EntityManager<Pitomec>.Instance.Find(p => p.Vozrast == 10 || p.Vid == new DropDownItem("Кошка")) //Сравнение идет только с конечными данными.
Неправильные выражения
EntityManager<Pitomec>.Instance.Find(p => p.Vozrast > 10) //Нельзя использовать оператор сравнения "больше".
EntityManager<Pitomec>.Instance.Find(p => p.Imya == p.Hozyain.Name) //Нельзя использовать переменные искомого объекта справа и слева от оператора сравнения.
EntityManager<Pitomec>.Instance.Find(p => p.Hozyain.Name == "Иван") //Нельзя искать по вложенным полям объектов.

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

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

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

Каждый фильтр в системе наследует базовый интерфейс IFilter, в котором обязательно есть строковое поле Query. Используя в этом поле запрос на языке EQL, можно выбрать объекты по достаточно гибкому условию.

//Получаем менеджер для объекта Питомец
var manager = EntityManager<Pitomec>.Instance;

//Создаем базовый фильтр и заполняем поле запроса
var filter = new Filter()
{
    Query = "(Vid IS NULL OR Vozrast > 25) AND Hozyain IN (Name = ’Иван’)"
}

//Получаем результирующий список объектов по фильтру
var pets = manager.Find(filter);

При этом можно не создавать фильтр, а сразу задавать EQL-запрос в методе Find как параметр. Однако при этом теряется возможность указывать FetchOptions.

//Получаем менеджер для объекта Питомец
var manager = EntityManager<Pitomec>.Instance;

//Получаем результирующий список объектов по фильтру
var pets = manager.Find("(Vid IS NULL OR Vozrast > 25) AND Hozyain IN (Name = ’Иван’)");

Также EQL-фильтр можно использовать в PublicAPI методах или через создание фильтра с указанием параметра Query и FetchOptions, или передавая EQL-запрос напрямую:

//Создаем фильтр с EQL-запросом и получаем искомый список объектов с указанием FetchOptions
var filter = PublicAPI.Objects.UserObjects.UserPitomec.Filter().Query("(Vid IS NULL OR Vozrast > 25) AND Hozyain IN (Name = ’Иван’)").Filter; 
var petsWithFilter = PublicAPI.Objects.UserObjects.UserPitomec.Find(filter, FetchOptions.All);

//Получаем искомый список объектов напрямую через EQL
var pets = PublicAPI.Objects.UserObjects.UserPitomec.Find("(Vid IS NULL OR Vozrast > 25) AND Hozyain IN (Name = ’Иван’)");

В этом примере мы ищем все объекты типа Питомец для которых справедливо, что Вид имеет пустое значение ИЛИ Возраст больше 25 И Хозяин удовлетворяет условию, что Имя равно "Иван". Давайте разберем по частям:

  • Вид имеет пустое значение ИЛИ Возраст больше 25 – это выражение описывается как Vid IS NULL OR Vozrast > 25;
  • Хозяин удовлетворяет условию, что Имя равно "Иван" – это выражение описывается как Hozyain IN (Name = ’Иван’).
При использовании языка EQL можно формировать подзапросы и делать дополнительную фильтрацию по вложенным полям объектов.

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

Класс FetchOptions позволяет ограничивать получаемую выборку по фильтру, что позволяет проводить поиск быстрее и уменьшить нагрузку сервера в случае, если нет необходимости получать все объекты по заданному фильтру. У класса FetchOptions есть два предустановленных набора значений:

  • FetchOptions.All – позволяет получать все данные по фильтру;
  • FetchOptions.First – вернет только первую сущность (с наименьшим Id), удовлетворяющую условиям фильтрации. Такой запрос будет выполняться значительно быстрее, потому что данные по другим объектам, удовлетворяющим условиям заданного фильтра, загружаться не будут.

Кроме того, можно самому настраивать параметр FetchOptions гибким образом, создавая объект данного класса вручную, например:

//Следующая настройка вернет не более первых 3-х сущностей, удовлетворяющих условиям поиска
    var fetch1 = new FetchOptions(0, 3);
    //Вернет не более первых 5-и сущностей, удовлетворяющие условиям поиска, начиная с третей сущности
    var fetch2 = new FetchOptions(2, 5);
    //Получит самых старых трех животных, удовлетворяющим критерям фильтра
    var fetch3 = new FetchOptions(0, 3, ListSortDirection.Descending, "Vozrast"); 

Первый аргумент конструктора класса FetchOptions определяет номер первой по порядку получаемой записи, нумерация идет с 0.

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

Третий аргумент задает направление сортировки:

  • ListSortDirection.Descending – по убыванию;
  • ListSortDirection. Ascending – по возрастанию.

Перечисление ListSortDirection располагается в пространстве имен System.ComponentModel.

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

Большинство реализаций метода Find() позволяет проводить поиск без указания фильтра с одним только параметром FetchOptions, например:

var oldestAnimals = PublicAPI.Objects.UserObjects.UserPitomec.Find(new FetchOptions(0, 3, ListSortDirection.Descending, "Vozrast"));

Дополнительные материалы: