Структура модели данных и менеджеры сущностей (объектов)

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

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

Объекты иногда еще называют Сущностями, на самом деле это одно и то же, поэтому не путайтесь с подменой понятий

Особенностью системы является тесная интеграция с платформой .NET и в объектной модели это выражено больше всего

  • Все пользовательские модели хранятся в сборке .NET (EleWise.ELMA.ConfigurationModel)
  • Для каждого объекта в системе создается отдельный класс со своим именем
    • Для пользовательских объектов с вкладки Объекты
    • Для каждого типа документа из документооборота
    • Для каждого процесса из раздела процессы
  • Для каждого свойства объекта (или контекстной переменной в процессе) создается отдельное свойство в классе

Таким образом использовать объекты в сценариях становится очень просто - вы используете простые классы и их свойства. Все классы объектов системы наследуют обязательно базовый интерфейс IEntity.

/// <summary>
/// Интерфейс объекта с идентификатором
/// </summary>
public interface IIdentified
{
    /// <summary>
    /// Получить нетипизированное значение идентификатора у сущности
    /// </summary>
    /// <returns>Значение идентификатора</returns>
    object GetId();

    /// <summary>
    /// Установить нетипизированное значение идентификатора
    /// </summary>
    /// <param name="id">Значение идентификатора</param>
    void SetId(object id);
}
public interface IEntity : IIdentified
{
    ///<summary>
    /// Возвращает строковое представление сущности
    ///</summary>
    ///<param name="format">Формат отображения, свойства доступны через {$Имя свойства}</param>
    ///<returns>Строка представляющая сущность</returns>
    string ToString(string format);


    /// <summary>
    /// Получить значение свойства по его уникальному идентификатору
    /// </summary>
    /// <param name="propertyUid">Уникальный идентификатор свойства</param>
    /// <returns>Значение свойства</returns>
    object GetPropertyValue(Guid propertyUid);

    /// <summary>
    /// Получить значение свойства по его уникальному идентификатору
    /// </summary>
    /// <typeparam name="T">Тип свойства</typeparam>
    /// <param name="propertyUid">Уникальный идентификатор свойства</param>
    /// <returns>Значение свойства</returns>
    T GetPropertyValue<T>(Guid propertyUid);

    /// <summary>
    /// Установить значение свойства по его уникальному идентификатору
    /// </summary>
    /// <param name="propertyUid">Уникальный идентификатор свойства</param>
    /// <param name="value">Значение свойства</param>
    void SetPropertyValue(Guid propertyUid, object value);
    
    /// <summary>
    /// Получить настройки свойства (возвращаются настройки для данного экземпляра сущности, либо копия общих)
    /// </summary>
    /// <param name="propertyUid">Уникальный идентификатор свойства</param>
    TSettings GetPropertySettings<TSettings>(Guid propertyUid)
        where TSettings : TypeSettings;
    
    /// <summary>
    /// Получить настройки свойства (возвращаются настройки для данного экземпляра сущности, либо копия общих)
    /// </summary>
    /// <param name="propertyUid">Уникальный идентификатор свойства</param>
    TypeSettings GetPropertySettings(Guid propertyUid);

    /// <summary>
    /// Получить настройки свойства (возвращаются настройки для данного экземпляра сущности, либо копия общих)
    /// </summary>
    /// <param name="propertyUid">Уникальный идентификатор свойства</param>
    /// <param name="defaultSettings">Настройки по умолчанию</param>
    TypeSettings GetPropertySettings(Guid propertyUid, TypeSettings defaultSettings);

    /// <summary>
    /// Получить настройки свойства, сохраненные для данного объекта. Если их нет - то возвращается null
    /// </summary>
    /// <param name="propertyUid">Уникальный идентификатор свойства</param>
    TypeSettings LoadPropertyInstanceSettings(Guid propertyUid);

    /// <summary>
    /// Сохранить настройки свойства для данного объекта
    /// </summary>
    /// <param name="propertyUid">Уникальный идентификатор свойства</param>
    /// <param name="settings">Настройки</param>
    void SavePropertyInstanceSettings(Guid propertyUid, TypeSettings settings);

    /// <summary>
    /// Загрузить хранилище настроек свойств даннного объекта
    /// </summary>
    /// <param name="createIfNotExists">Создавать ли хранилище, если оно не существует</param>
    /// <returns></returns>
    ITypeSettingsInstanceStore LoadSettingsInstanceStore(bool createIfNotExists = false);

    /// <summary>
    /// Получить сущности, которые содержатся в данной сущности (например, элементы блока, настройки сущности)
    /// </summary>
    /// <returns></returns>
    IEnumerable<IEntity> GetContainedEntities();

    /// <summary>
    /// Получить корневую сущность (если это элемент блока, то возвращается первый родитель)
    /// </summary>
    /// <returns></returns>
    IEntity GetRootEntity();

    /// <summary>
    /// Сохранить сущность
    /// </summary>
    void Save();

    /// <summary>
    /// Удалить сущность
    /// </summary>
    void Delete();

    /// <summary>
    /// Обновить сущность из БД
    /// </summary>
    void Refresh();

    /// <summary>
    /// Сущьность не сохранялась в базе
    /// </summary>
    /// <returns></returns>
    bool IsNew();
}
 

Системные объекты

В системе есть много предустановленных (системных) объектов. Эти объекты раскиданы по различным пространствам имен и сборкам (в соответствии со структурой модулей). Однако вы всегда можете найти нужный вам объект в дизайнере на вкладке Объекты. Вся информация, которая вам понадобится для работы с объектом в сценарии, находится на вкладке Общие под группой Структура данных.

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

Так для объекта Пользователь на скриншоте, вы видите, что пространство имен задано EleWise.ELMA.Security.Models - это значит, что в сценариях вам нужно будет прописать это пространство имен как подключаемое using EleWise.ELMA.Security.Models.

Пользовательские объекты

Помимо системных объектов в системе, конечно, можно создавать свои собственные объекты. Все пользовательские объекты, как уже было сказано, создаются в отдельном пространстве имен EleWise.ELMA.ConfigurationModel.

Для процессов вы можете найти эту информацию на вкладке Настройки при редактировании процесса

Обратите внимание, что класс формируется для контекстных переменных процесса и находится в пространстве имен  EleWise.ELMA.Model.Entities.ProcessContext

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

Менеджеры сущностей (объектов)

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

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

public interface IEntityManager
{

    /// <summary>
    /// Создать новую сущность (без записи в БД)
    /// </summary>
    /// <returns>Сущность</returns>
    object Create();

    /// <summary>
    /// Проверить, является ли сущность новой (не сохраненной в БД)
    /// </summary>
    /// <param name="obj">Сущность</param>
    /// <returns>True, если новая</returns>
    bool IsNew(object obj);

    /// <summary>
    /// Загрузить по идентификатору. Если не найдена - вызывается исключение
    /// </summary>
    /// <param name="id">Идентификатор</param>
    /// <returns>Сущность</returns>
    object Load(object id);

    /// <summary>
    /// Загрузить по идентификатору. Если не найдена - возвращается null
    /// </summary>
    /// <param name="id">Идентификатор сущности</param>
    /// <returns>Сущность или null</returns>
    object LoadOrNull(object id);

    /// <summary>
    /// Загрузить по уникальному идентификатору. Если не найдена - вызывается исключение
    /// </summary>
    /// <param name="uid">Уникальный идентификатор сущности</param>
    /// <returns>Сущность</returns>
    object Load(Guid uid);

    /// <summary>
    /// Загрузить по уникальному идентификатору. Если не найдена - возвращается null
    /// </summary>
    /// <param name="uid">Уникальный идентификатор сущности</param>
    /// <returns>Сущность или null</returns>
    object LoadOrNull(Guid uid);

    /// <summary>
    /// Сохранить сущность в БД
    /// </summary>
    /// <param name="obj">Сущность</param>
    void Save(object obj);

    /// <summary>
    /// Удалить сущность в БД
    /// </summary>
    /// <param name="obj">Сущность</param>
    void Delete(object obj);

    /// <summary>
    /// Удалить сущность в БД по запросу.
    /// ВНИМАНИЕ!!! Не использовать для больших массивов, т.к. данные сначала загружаются в память из БД и только потом удаляются.
    /// </summary>
    /// <param name="query">Запрос</param>
    void Delete(string query);

    /// <summary>
    /// Удалить все сущности из БД
    /// </summary>
    void DeleteAll();

    /// <summary>
    /// Обновить (перечитать) сущность из БД
    /// </summary>
    /// <param name="obj">Сущность</param>
    void Refresh(object obj);

    /// <summary>
    /// Найти все сущности
    /// </summary>
    /// <returns>Список всех сущностей</returns>
    IEnumerable FindAll();

    /// <summary>
    /// Найти все сущности по идентификаторам из массива
    /// </summary>
    /// <param name="idArray">Массив идентификаторов</param>
    /// <returns>Список сущностей</returns>
    IEnumerable FindByIdArray(object[] idArray);

    /// <summary>
    /// Найти сущности в соответствии с параметрами выборки
    /// </summary>
    /// <param name="fetchOptions">Параметры выборки</param>
    /// <returns>Список найденных сущностей</returns>
    IEnumerable Find(FetchOptions fetchOptions);

    /// <summary>
    /// Найти сущности в соответствии с фильтром и параметрами выборки
    /// </summary>
    /// <param name="filter">Фильтр</param>
    /// <param name="fetchOptions">Параметры выборки</param>
    /// <returns>Список найденных сущностей</returns>
    IEnumerable Find(IEntityFilter filter, FetchOptions fetchOptions);

    /// <summary>
    /// Найти сущности в соответствии с фильтром и параметрами выборки
    /// </summary>
    /// <param name="filter">Фильтр</param>
    /// <param name="fetchOptions">Параметры выборки</param>
    /// <typeparam name="T">Тип сущности</typeparam>
    /// <returns>Список найденных сущностей</returns>
    IEnumerable<T> Find<T>(IEntityFilter filter, FetchOptions fetchOptions);

    /// <summary>
    /// Возвращает количество всех сущностей
    /// </summary>
    /// <returns>Количество всех сущностей</returns>
    long Count();

    /// <summary>
    /// Возвращает количество сущностей по фильтру
    /// </summary>
    /// <param name="filter">Фильтр</param>
    /// <returns>Количество сущностей по фильтру</returns>
    long Count(IEntityFilter filter);

    /// <summary>
    /// Получить проекцию значений свойства
    /// </summary>
    /// <typeparam name="PT">Тип свойства</typeparam>
    /// <param name="propertyName">Имя свойства</param>
    /// <returns>Список выбранных значений</returns>
    ICollection<PT> Projection<PT>(string propertyName);

    /// <summary>
    /// Установить фильтр в NHibernate критерию
    /// </summary>
    /// <param name="criteria"></param>
    /// <param name="filter"></param>
    void SetupFilter(ICriteria criteria, IEntityFilter filter);

    /// <summary>
    /// Создать detached-критерию
    /// </summary>
    /// <param name="fetchOptions">Параметры выборки</param>
    /// <param name="type">Тип, для которого производится выборка</param>
    /// <param name="alias">Псевдоним</param>
    /// <param name="filter"></param>
    /// <returns>Критерий</returns>
    DetachedCriteria CreateDetachedCriteria(FetchOptions fetchOptions, Type type, string alias = null, IEntityFilter filter = null);

    /// <summary>
    /// Генерировать исключение безопасности 
    /// </summary>
    /// <param name="text"></param>
    /// <param name="id"></param>
    /// <returns></returns>
    SecurityException CreateSecurityException(string text, object id);
}
 

Для упрощения работы с типизированными данными введен типизированный интерфейс менеджера IEntityManager<T>

 

public interface IEntityManager<T> : IEntityManager where T : IEntity
{

    /// <summary>
    /// Создать новую сущность (без записи в БД)
    /// </summary>
    /// <returns>Сущность</returns>
    [NotNull]
    new T Create();

    /// <summary>
    /// Загрузить по идентификатору. Если не найдена - вызывается исключение
    /// </summary>
    /// <param name="id">Идентификатор</param>
    /// <returns>Сущность</returns>
    [NotNull]
    new T Load(object id);

    /// <summary>
    /// Загрузить по идентификатору. Если не найдена - возвращается null
    /// </summary>
    /// <param name="id">Идентификатор сущности</param>
    /// <returns>Сущность или null</returns>
    [CanBeNull]
    new T LoadOrNull(object id);

    /// <summary>
    /// Загрузить по уникальному идентификатору. Если не найдена - вызывается исключение
    /// </summary>
    /// <param name="uid">Уникальный идентификатор сущности</param>
    /// <returns>Сущность</returns>
    [NotNull]
    new T Load(Guid uid);

    /// <summary>
    /// Загрузить по уникальному идентификатору. Если не найдена - возвращается null
    /// </summary>
    /// <param name="uid">Уникальный идентификатор сущности</param>
    /// <returns>Сущность или null</returns>
    [CanBeNull]
    new T LoadOrNull(Guid uid);

    /// <summary>
    /// Сохранить сущность в БД
    /// </summary>
    /// <param name="obj">Сущность</param>
    void Save(T obj);

    /// <summary>
    /// Удалить сущность в БД
    /// </summary>
    /// <param name="obj">Сущность</param>
    void Delete(T obj);

    /// <summary>
    /// Обновить сущность
    /// </summary>
    /// <param name="obj">Сущность</param>
    void Refresh(T obj);

    /// <summary>
    /// Сущность не сохранялась в базе
    /// </summary>
    /// <param name="obj">Сущность</param>
    bool IsNew(T obj);

    /// <summary>
    /// Найти все сущности
    /// </summary>
    /// <returns>Список всех сущностей</returns>
    new ICollection<T> FindAll();

    /// <summary>
    /// Найти все сущности по идентификаторам из массива
    /// </summary>
    /// <param name="idArray">Массив идентификаторов</param>
    /// <returns>Список сущностей</returns>
    new ICollection<T> FindByIdArray(object[] idArray);

    /// <summary>
    /// Найти сущности в соответствии с параметрами выборки
    /// </summary>
    /// <param name="fetchOptions">Параметры выборки</param>
    /// <returns>Список найденных сущностей</returns>
    new ICollection<T> Find(FetchOptions fetchOptions);

    /// <summary>
    /// Найти сущности в соответствии с фильтром и параметрами выборки
    /// </summary>
    /// <param name="filter">Фильтр</param>
    /// <param name="fetchOptions">Параметры выборки</param>
    /// <returns>Список найденных сущностей</returns>
    new ICollection<T> Find(IEntityFilter filter, FetchOptions fetchOptions);

    /// <summary>
    /// Поиск по условию в выражении
    /// </summary>
    /// <param name="condition">Условие поиска</param>
    /// <returns>Список сущностей удовлетвояющих условию поиска</returns>
    ICollection<T> Find(Expression<Func<T, bool>> condition);

    /// <summary>
    /// Поиск по условию в выражении
    /// </summary>
    /// <param name="condition">Условие поиска</param>
    /// <returns>Список сущностей удовлетвояющих условию поиска</returns>
    ICollection<TResult> Find<TResult>(Expression<Func<TResult, bool>> condition);
     /// <summary>
     /// Поиск по условию в выражении
     /// </summary>
     /// <param name="condition">Условие поиска</param>
     /// <param name="fetchOptions">Параметры выборки</param>
     /// <returns>Список сущностей удовлетвояющих условию поиска</returns>
     ICollection<T> Find(Expression<Func<T, bool>> condition, FetchOptions fetchOptions);

     /// <summary>
     /// Поиск по условию в выражении
     /// </summary>
     /// <param name="condition">Условие поиска</param>
     /// <param name="fetchOptions">Параметры выборки</param>
     /// <returns>Список сущностей удовлетвояющих условию поиска</returns>
     ICollection<TResult> Find<TResult>(Expression<Func<TResult, bool>> condition, FetchOptions fetchOptions);
}
C# Code
 

Есть еще несколько базовых интерфейсов, но их описание вы можете посмотреть в справке по API

Общее использование менеджеров

Для использования менеджера объекта вам прежде всего нужно знать класс этого объекта. Предположим, что нам нужно получить менеджер для объекта Pitomec из пространства имен EleWise.ELMA.ConfigurationModel (этот объект мы создали ранее в дизайнере), в этом случае обращение к менеджеру будет выглядеть так

var manager = EleWise.ELMA.Model.Managers.EntityManager<EleWise.ELMA.ConfigurationModel.Pitomec>.Instance;
 

Однако можно вынести пространства имен в блок using и тогда получение менеджера будет такое

var manager = EntityManager<Pitomec>.Instance;
 

Тут мы используем статический класс EntityManager<T> для получения менеджера по типу объекта. Вообще же есть несколько способов получить менеджер для объекта

  • Использовать статический класс EntityManager<T>
  • Использовать локатор и получать менеджер как сервис по интерфейсу Locator.GetServiceNotNull<IEntityManager<T>>()
  • Получать через локатор менеджер с указанием системного класса менеджера Locator.GetServiceNotNull<UserManager>()

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

var manager = EntityManager<Pitomec>.Instance;
var filter = new PitomecFilter()
{
    Vozrast = new EleWise.ELMA.Model.Ranges.Int64Range()
    {
        From = 5
    },
    Vid = new DropDownItem("Кошка")
};
var pets = manager.Find(filter);
foreach (var pet in pets) 
{
    pet.Pozhiloe = true;
    pet.Save();
}
 

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

Системные менеджеры

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

/// <summary>
/// Проверить, является ли один пользователь начальником другого
/// </summary>
/// <param name="cheifUser">начальник</param>
/// <param name="subordinateUser">подчиненный</param>
/// <returns></returns>
public bool IsSubordinateUser([NotNull] Models.IUser cheifUser, [NotNull] Models.IUser subordinateUser)
/// <summary>
/// Проверяет, есть ли начальник у пользователя
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public bool HasChiefForUser([NotNull] Models.IUser user)
/// <summary>
/// Получить пользователей по элементу оргстуруктуры, если элемент является должностью, то возвращается пользователь соотв. должности,
/// если отдел то все сотрудники отдела в т.ч. вложенные должности
/// </summary>
/// <param name="departament"></param>
/// <returns></returns>
public IEnumerable<Models.IUser> GetUsersByDepartament([NotNull] IOrganizationItem departament)
 

и некоторые другие, о которых можно почитать в справке API.

Менеджеры для системных объектов получить достаточно просто, если сам класс объекта лежит в пространстве имен EleWise.ELMA.Security.Models то соответствующий менеджер, скорее всего, находится в соседнем пространстве имен EleWise.ELMA.Security.Managers (т.е. надо заменить пространство Models на Managers).

Базовый менеджер

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

public class EntityManager<T, IdT> : AbstractNHEntityManager<T, IdT> where T : IEntity<IdT>
{
}

Данный менеджер уже содержит определенный набор методов для работы с объектами. Основные методы:

//Загрузка объекта из БД по уникальному идентификатору (тип идентификатора настраивается в редакторе сущностей)
//Если объекта с заданным id не существует, то кидается исключение
public sealed override T Load(IdT id)

//Загрузка объекта из БД по уникальному идентификатору (тип идентификатора настраивается в редакторе сущностей)
//Если объекта с заданным id не существует, то возвращается null 
public sealed override T LoadOrNull(IdT id)

//Сохранение объекта в БД
public sealed override void Save(T obj)

//Записать изменения в БД
public sealed override void Update(T obj)

//Удалить объект из БД
public sealed override void Delete(T obj)

//Удалить все объекты данного типа
public override void DeleteAll()

//Перечитать объект из БД (при этом обновляется кэш по этому объекту)
public sealed override void Refresh(T obj)

//Получить все объекты данного типа
public sealed override ICollection<T> FindAll()

//Получить все объекты, удовлетворяющие фильтру (учитывается тот фильтр, который генерируется для типа объекта в редакторе сущностей)
public sealed override ICollection<T> Find(Filter filter, FetchOptions fetchOptions)

Для того чтобы получить базовый менеджер по типу объекта используется: EntityManager<TEntity>.Instance

Переопределение базового менеджера

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

public class UserManager : EntityManager<User, long>
{
//метод для создания папки "Мои документы" для пользователя user
private static void CreateFolders(User user)
{
if (MyDocumentFolderManager.Instance.LoadByUser(user) == null)
{
MyDocumentFolderManager.Instance.CreateForUser(user);
}
}

//Переопределяем метод сохранения пользователя в БД
protected override void SaveEntity(User obj)
{
//Если объект новый
if (obj.Id == 0)
//Шифруем пароль
obj.Password = EncryptionHelper.GetMd5Hash(obj.Password);
//Вызываем базовый метод сохранения объекта в БД
base.SaveEntity(obj);
if (obj.Id > 1)
//Создаем папку "Мои документы" (если она еще не создана)
CreateFolders(obj);
}
}

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