«Накопительный» тип данных (rollup) появился в Microsoft Dynamics CRM 2015. Он позволяет получать агрегированную информацию по линии потомок-родитель. Такая функциональность несомненно востребована на рынке, поскольку обновление информации из «потомков» на «родителе» всегда представляло техническую сложность, но было востребовано и востребовано сейчас во многих бизнес задачах.

Rollup поле определяется как обычное поле в CRM, но с формулой для вычисления агрегированных значений связанных записей. При создании и обновлении поля создается системная задача, которая вычисляет значение этого поля на всех доступных записях с этим полем. В дальнейшем обновление значений этого поля производится инкрементно каждый час при помощи другой системной задачи, так же создаваемой автоматически ядром CRM. В данной работе рассмотрен один из аспектов работы с накопительными полями, а именно что происходит в момент создания/обновления такого поля и что может пойти не так.

Информацию про rollup поля можно почерпнуть из официальной и не очень информации. Там, однако, не все описано. Когда поле создается (обновляется) CRM регистрирует system job для начального вычисления значения поля во всех доступных записях. Эта задача автоматически планируется к запуску через 24 часа. Как показало исследование трейсов обновление поля ведет к тому, что значение InitialValueCalculationStatus для этого поля в таблице dbo.RollupPropertiesBase становится равным 0. После того как задача массового обновления отработает, это значение меняется на 3. Набор значений этого поля не соответствует набору значений поля _State, доступного через мета-дату. Начальное состояние «накопительного» поля не доступно через публичный API, что делает диагностику состояния такого поля затруднительной для облачных систем, где нет доступа к базе данных.

Первоначальный расчет значений созданного/обновленного поля полностью контролируется внутри системы. Администратор может проверить наличие системной задачи, остановить или изменить время ее запуска, но контролировать ее исполнение кроме как через проверку ее статуса, нет никакой возможности. В случае если по каким-то причинам задача не сработала как надо, а такое, как показала практика, бывает, поле InitialValueCalculationStatus не становится равным 3. При этом регулярное (инкрементное) обновление поля через другую системную задачу производится и демонстирует успешное поведение – статус Success и никаких ошибок. Это происходит потому что задача регулярного обновления проверяет перед началом вычислений было ли проведено изначальное вычисление поля (InitialValueCalculationStatus = 3), и если нет, то пропускает это поле. Таким образом в системе могут появится авто-обновляемые поля, которые не обновляются и показывают устаревшую информацию.
В качестве решения проблемы можно использовать обновление определения поля либо через CRM интерфейс либо через использование RetrieveAttributeRequest и UpdateAttributeRequest. Простое пересохранение поля вызывает обнуление значения InitialValueCalculationStatus и создание задачи массового обновления значений поля.

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

var request = new RetrieveAllEntitiesRequest()
{
   EntityFilters = Microsoft.Xrm.Sdk.Metadata.EntityFilters.Attributes,
   RetrieveAsIfPublished = true,
};
var response = (RetrieveAllEntitiesResponse)crmService.Execute(request);

С последующим извлечением атрибутов из полученных записей (EntityMetadata entity):

var rollupAttributes = entity.Attributes.Where(a => a.SourceType == 2);

Теперь надо извлечь Date атрибут. Каждое rollup поле состоит из трех – само поле с формулой и типом 2 (SourceType == 2), поля Date и поля State. Нас интересует поле Date, поскольку оно содержит время последнего обновления поля. Изменение «Накопительных» полей не затрагивают поле ModifiedOn.

foreach (var rollupAttribute in rollupAttributes)
{
   var dateAttribute = entity.Attributes.Where(a => a.LogicalName.ToLower() == rollupAttribute.LogicalName.ToLower() + "_date").FirstOrDefault();
 }

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

var qe = new QueryExpression();

qe.EntityName = entity.LogicalName;
qe.ColumnSet = new ColumnSet();
qe.TopCount = 1;

var order = new OrderExpression();
order.AttributeName = rollupField.DateField.LogicalName;
order.OrderType = OrderType.Descending;

qe.Orders.Clear();
qe.Orders.Add(order);
qe.ColumnSet.Columns.Clear();
qe.ColumnSet.Columns.Add(rollupField.DateField.LogicalName);

var ec = crmService.RetrieveMultiple(qe);

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

if (ec.Entities.Any())
{
   var lastUpdateDateRaw = ec.Entities[0].Attributes[rollupField.DateField.LogicalName].ToString();
   DateTime lastUpdateDate;
   if (DateTime.TryParse(lastUpdateDateRaw, out lastUpdateDate))
   {
      if (lastUpdateDate.ToLocalTime() < DateTime.Today.AddDays(-1))
      {
         //Put your logic here
      }
   }
}

Здесь есть небольшой подвох, связанный с возможностью обновить значение «накопительного» поля вручную, доступный пользователям. В таком случае предложенный анализ необходимо усложнить – выводить статистику по набору последних обновления поля, возможно анализировать связанные записи, в общем проявить фантазию в зависимости от обстоятельств.

Комментарии (0)