На данный пример меня натолкнула недавняя статья Панкрашова Дмитрия "DirectumRX. Подробная история в карточке сущности", а точнее комментарии к этой статье.
В материале Дмитрия описывается пример записи в историю сущности, при изменении какого-либо свойства этой сущности и в комментариях были заданы несколько вопросов:
Для того чтобы разобраться в этом вопросе, я сделал небольшой пример, который должен ответить на поставленные вопросы.
Весь код можно разместить в серверном или разделяемом слое какого-нибудь модуля (лично у меня серверный слой тестового модуля).
1) Для того, чтобы работать с метаданными необходимо подключить библиотеку Sungero.Domain.Shared.dll
using Sungero.Domain.Shared;
2) Далее размещаем в коде 2 функции ChangeRequisites() и CheckRequisite()
/// <summary>
/// Получить список измененных свойств объекта
/// </summary>
/// <param name="entity">Сущность</param>
/// <returns>Список измененных свойств</returns>
[Public, Remote(IsPure=true)]
public static List<string> ChangeRequisites(Sungero.Domain.Shared.IEntity entity)
{
var changeList = new List<string>();
//Получаем "Тип" объекта
var objType = entity.GetType().GetFinalType();
//Получаем "Метаданные" объекта
var objMetadata = objType.GetEntityMetadata();
//TODO: Ниже, в objMetadata.Properties можно добавить фильтрацию по наименованиям реквизитов (либо выбрать, либо исключить из выбора определенные реквизиты)
//Пример 1: objMetadata.Properties.Where(p => requsitesList.Contains(p.Name));
//Пример 2: objMetadata.Properties.Where(p => !excludeRequsitesList.Contains(p.Name));
//Получаем свойства объекта
var properties = objMetadata.Properties;
foreach (var propertyMetadata in properties)
{
//Если текущее свойство это коллекция, то обработает ее отдельно
if (propertyMetadata.PropertyType == Sungero.Metadata.PropertyType.Collection)
{
//Получим значение коллекции
var collectionValue = (Sungero.Domain.Shared.IChildEntityCollection<Sungero.Domain.Shared.IChildEntity>)propertyMetadata.GetValue(entity);
//Переберем строки коллекции
foreach (Sungero.Domain.Shared.IChildEntity line in collectionValue)
{
System.Type lineType = line.GetType();
var lineMetadata = lineType.GetEntityMetadata();
//TODO: Ниже, в lineMetadata.Properties можно добавить фильтрацию по наименованиям реквизитов (либо выбрать, либо исключить из выбора определенные реквизиты)
//Пример 1: lineMetadata.Properties.Where(p => collectionRequsitesList.Contains(p.Name));
//Пример 2: lineMetadata.Properties.Where(p => !collectionExcludeRequsitesList.Contains(p.Name));
//Получаем свойства строки коллекции
var lineProperties = lineMetadata.Properties;
foreach (var linePropertyMetadata in lineProperties)
{
//Если значение свойства это RootEntity, то пропустим обработку этого свойства
if (Equals(entity, linePropertyMetadata.GetValue(line)))
continue;
var checkResult = CheckRequisite(line, linePropertyMetadata);
if (!string.IsNullOrEmpty(checkResult))
changeList.Add(string.Format("Коллекция {0}. {1}", propertyMetadata.GetLocalizedName(), checkResult));
}
}
}
else
{
var checkResult = CheckRequisite(entity, propertyMetadata);
if (!string.IsNullOrEmpty(checkResult))
changeList.Add(checkResult);
}
}
return changeList;
}
/// <summary>
/// Проверка изменения свойства
/// </summary>
/// <param name="entity">Сущность или строка коллекции</param>
/// <param name="propertyMetadata">Метаданные свойства</param>
/// <returns>Сообщение об изменении свойства, иначе string.Empty</returns>
private static string CheckRequisite(Sungero.Domain.Shared.IEntity entity, Sungero.Metadata.PropertyMetadata propertyMetadata)
{
var stateProperties = entity.State.Properties;
var propertyState = stateProperties.GetType().GetProperty(propertyMetadata.Name).GetValue(stateProperties);
//Получим текущее значение свойства
var newValue = propertyMetadata.GetValue(entity);
//Получим предыдущее значение свойства, до сохранения
var originalValue = propertyState.GetType().GetProperty("OriginalValue").GetValue(propertyState);
if (Equals(newValue, originalValue) || (string.IsNullOrEmpty(newValue.ToString()) && string.IsNullOrEmpty(originalValue.ToString())))
return string.Empty;
//Если тип свойства Enumeration, то получим локализированные значения
if (propertyMetadata.PropertyType == Sungero.Metadata.PropertyType.Enumeration)
{
var infoProperties = entity.Info.Properties;
var propertyInfo = infoProperties.GetType().GetProperty(propertyMetadata.Name).GetValue(infoProperties) as Sungero.Domain.Shared.EnumPropertyInfo;
newValue = propertyInfo.GetLocalizedValue(newValue as Sungero.Core.Enumeration?);
originalValue = propertyInfo.GetLocalizedValue(originalValue as Sungero.Core.Enumeration?);
}
var formatedNewValue = string.Format(" Новое значение: {0}.", string.IsNullOrEmpty(newValue.ToString()) ? "Пусто" : newValue);
var formatedOldValue = string.Format(" Прежнее значение: {0}.", string.IsNullOrEmpty(originalValue.ToString()) ? "Пусто" : originalValue);
return string.Format("Изменение свойства \"{0}\".{1}{2}", propertyMetadata.GetLocalizedName(), formatedNewValue, formatedOldValue);
}
Для теста я перекрыл справочник DocumentRegister, добавил на ленту действие, а в действии написал вызов функции
var changeList = finex.TestModule.PublicFunctions.Module.Remote.ChangeRequisites(_obj);
Dialogs.ShowMessage(string.Join("\n", changeList));
Запускаем и проверяем.
Для примера я взял запись "Служебные записки" .
Меняем в карточке свойства: Индекс, Состояние, элемент номера в коллекции "Формат номера".
Нажимаем на нашу тестовую кнопку "123" и получаем результат:
Код в примере постарался сделать максимально универсальным с относительно подробными комментариями. Если я чего-то не предусмотрел или у Вас появятся вопросы по реализации, то пишите в комментариях, постараюсь ответить.
P.S. Полные namespace в коде оставлены специально, для понимания, что и откуда берется.
Спасибо. Познавательная статья.
Возникает только вопрос, каким образом производился первоначальный анализ структуры метаданных?
Спасибо действительно полезная статья.
Подскажите, пожалуйста, при сборке ругается на строку:
Конкретно на collectionValue.
Отсутствует обязательный для компилятора член "Microsoft.CSharp.RuntimeBinder.Binder.Convert" (CS0656)
Собралось только с помощью приведения к конкретному типу:
Антон, Да, начиная с 4.1 dynamic перестал работать.
Konstantin, Описание метаданных объекта (его свойств и действий) можно посмотреть в MTD файле этого объекта (mtd который находится в Shared).
Еще можно смотреть свойства, методы или namespace получая конкретные объекты где-то в коде, т.е. взять условно "Сотрудника", получить какую-нибудь запись и посмотреть в коде что есть и что доступно, после чего код можно удалить.
Антон, Sungero.Domain.Shared.IChildEntity - на var замени, он сам нужный тип поставит
Сергей, а есть возможность отслеживания в истории добавление или удаление строк коллекции?
True / False в Да / Нет кто-то заморочился переделать?
Станислав, у вас возникли сложности? На первый взгляд несложная задача. Проверить является ли свойство логическим и в зависимости от значения переопределить новое и старое значения.
Денис, это колхоз же типа перед формированием текста смотреть если тру меняем на да если фолс на нет) думал есть более изящное решение ?
И по коллекции кроме вывода столбца ид и выписывание в строку ид строчки есть варианты определить в какой строке коллекции изменения были ?
Станислав, я же написал "Проверить является ли свойство логическим...". В статье и пример реализации есть
а как можно локализовать комментарий?
как локализуется сумма при смене суммы. Сумма / Total Amount?
Вместо propertyMetadata.GetLocalizedName() надо чтоб заполнялась ресурсом? как это сделать?
пока что решил сделать всё на русском, но не получается получать имя перечисления на русском. как сделать?
return string.Format("\"{0}\": {1} -> {2}", propertyMetadata.GetLocalizedName(new System.Globalization.CultureInfo("ru-RU")), formatedOldValue, formatedNewValue);
По поводу Да/Нет придумал только так... если кто то придумает лучше давайте)
if (propertyMetadata.PropertyType == Sungero.Metadata.PropertyType.Boolean) { originalValue = Convert.ToBoolean(originalValue) ? "Да" : "Нет"; newValue = Convert.ToBoolean(newValue) ? "Да" : "Нет"; }
Ну и падало когда менялось значение на пустоту при попытке сделать tostring, убрал эти условия
if (Equals(newValue, originalValue) || (string.IsNullOrEmpty(newValue.ToString()) && string.IsNullOrEmpty(originalValue.ToString()))) return string.Empty;
Оставил только Equals
Иван, Да, такая возможность есть, ничего сложного.
Станислав, Ваш вопрос связан с метаданными и работой с ними.
У нас в телеграмме есть группа Directum RX Developer, там выложена презентация "Метаданные + Reflection.pptx", она как раз поможет ответить на подобного рода вопросы.
Станислав, по языку решил
Спасибо за статью!
Вопрос: как можно отследить и записать в историю информацию об удаленной записи из коллекции?
Святослав, У коллекции есть свойство Deleted, в котором лежат удаленные строки.
Собственно через него и можно отследить.
Авторизуйтесь, чтобы написать комментарий