/// <summary> /// Processes plugin for a type type rolled up /// </summary> /// <param name="plugin"></param> public void ExecuteRollupPlugin(XrmEntityPlugin plugin) { if (plugin.IsMessage(PluginMessage.Create, PluginMessage.Update, PluginMessage.Delete) && plugin.IsStage(PluginStage.PostEvent) && plugin.IsMode(PluginMode.Synchronous)) { var rollupsToProcess = GetRollupsForRolledupType(plugin.TargetType).ToArray(); var dictionaryForDifferences = new Dictionary <string, Dictionary <Guid, List <UpdateMeta> > >(); Action <string, Guid, string, object, LookupRollup> addValueToApply = (type, id, field, val, rollup) => { if (!dictionaryForDifferences.ContainsKey(type)) { dictionaryForDifferences.Add(type, new Dictionary <Guid, List <UpdateMeta> >()); } if (!dictionaryForDifferences[type].ContainsKey(id)) { dictionaryForDifferences[type].Add(id, new List <UpdateMeta>()); } dictionaryForDifferences[type][id].Add(new UpdateMeta(field, rollup, val)); }; foreach (var rollup in rollupsToProcess) { //capture required facts in the plugin context to process our ifs and elses var metConditionsBefore = XrmEntity.MeetsFilter(plugin.GetFieldFromPreImage, rollup.Filter); var meetsConditionsAfter = plugin.MessageName == PluginMessage.Delete ? false : XrmEntity.MeetsFilter(plugin.GetField, rollup.Filter); var linkedIdBefore = XrmEntity.GetLookupType(plugin.GetFieldFromPreImage(rollup.LookupName)) == rollup.RecordTypeWithRollup ? plugin.GetLookupGuidPreImage(rollup.LookupName) : null; var linkedIdAfter = plugin.MessageName == PluginMessage.Delete || XrmEntity.GetLookupType(plugin.GetField(rollup.LookupName)) != rollup.RecordTypeWithRollup ? null : plugin.GetLookupGuid(rollup.LookupName); var isValueChanging = rollup.FieldRolledup != null && plugin.FieldChanging(rollup.FieldRolledup); var isOrderByChanging = rollup.OrderByField != null && plugin.FieldChanging(rollup.OrderByField); if (AllowsDifferenceChange(rollup.RollupType)) { //this covers scenarios which require changing the value in a parent record if (linkedIdBefore.HasValue && linkedIdBefore == linkedIdAfter) { //the same record linked before and after if (metConditionsBefore && meetsConditionsAfter) { //if part of Rollup before and after if (isValueChanging) { //and the value is changing then apply difference if (rollup.RollupType == RollupType.Sum) { addValueToApply(rollup.RecordTypeWithRollup, linkedIdAfter.Value, rollup.RollupField, GetDifferenceToApply(plugin.GetFieldFromPreImage(rollup.FieldRolledup), plugin.GetField(rollup.FieldRolledup)), rollup); } else if (rollup.RollupType == RollupType.Count) { //for count only adjust if changing between null and not null if (plugin.GetFieldFromPreImage(rollup.FieldRolledup) == null) { addValueToApply(rollup.RecordTypeWithRollup, linkedIdAfter.Value, rollup.RollupField, 1, rollup); } else if (plugin.GetField(rollup.FieldRolledup) == null) { addValueToApply(rollup.RecordTypeWithRollup, linkedIdAfter.Value, rollup.RollupField, -1, rollup); } } } } if (!metConditionsBefore && meetsConditionsAfter) { //if was not part of Rollup before but is now apply the entire value if (rollup.RollupType == RollupType.Sum) { addValueToApply(rollup.RecordTypeWithRollup, linkedIdAfter.Value, rollup.RollupField, plugin.GetField(rollup.FieldRolledup), rollup); } else if (rollup.RollupType == RollupType.Count) { addValueToApply(rollup.RecordTypeWithRollup, linkedIdAfter.Value, rollup.RollupField, 1, rollup); } } if (metConditionsBefore && !meetsConditionsAfter) { //if was part of Rollup before but not now apply the entire value negative if (rollup.RollupType == RollupType.Sum) { addValueToApply(rollup.RecordTypeWithRollup, linkedIdAfter.Value, rollup.RollupField, GetNegative(plugin.GetFieldFromPreImage(rollup.FieldRolledup)), rollup); } else if (rollup.RollupType == RollupType.Count) { addValueToApply(rollup.RecordTypeWithRollup, linkedIdAfter.Value, rollup.RollupField, -1, rollup); } } } else { //different parent linked before and after if (linkedIdBefore.HasValue && metConditionsBefore) { //if was part of previous linked records Rollup then negate the previous value if (rollup.RollupType == RollupType.Sum) { addValueToApply(rollup.RecordTypeWithRollup, linkedIdBefore.Value, rollup.RollupField, GetNegative(plugin.GetFieldFromPreImage(rollup.FieldRolledup)), rollup); } else if (rollup.RollupType == RollupType.Count) { addValueToApply(rollup.RecordTypeWithRollup, linkedIdBefore.Value, rollup.RollupField, -1, rollup); } } if (linkedIdAfter.HasValue && meetsConditionsAfter) { //if part of new linked records Rollup then apply the entire value if (rollup.RollupType == RollupType.Sum) { addValueToApply(rollup.RecordTypeWithRollup, linkedIdAfter.Value, rollup.RollupField, plugin.GetField(rollup.FieldRolledup), rollup); } else if (rollup.RollupType == RollupType.Count) { addValueToApply(rollup.RecordTypeWithRollup, linkedIdAfter.Value, rollup.RollupField, 1, rollup); } } } } else { //these ones just recalculate on the parent record(s) var isDependencyChanging = false; switch (plugin.MessageName) { case PluginMessage.Delete: { isDependencyChanging = linkedIdBefore.HasValue && metConditionsBefore; break; } case PluginMessage.Update: { if (linkedIdBefore != linkedIdAfter || isValueChanging) { isDependencyChanging = true; } else if (isOrderByChanging) { isDependencyChanging = true; } else { isDependencyChanging = metConditionsBefore != meetsConditionsAfter; } break; } case PluginMessage.Create: { isDependencyChanging = linkedIdAfter.HasValue && (rollup.FieldRolledup == null || isValueChanging) && meetsConditionsAfter; break; } } if (isDependencyChanging) { var processPreReferenced = false; var processPostReferenced = false; //If they aren't the same do both if (!XrmEntity.FieldsEqual(linkedIdBefore, linkedIdAfter)) { processPreReferenced = true; processPostReferenced = true; } //else just do the first not null one else { if (linkedIdBefore.HasValue) { processPreReferenced = true; } else { processPostReferenced = true; } } if (processPreReferenced && linkedIdBefore.HasValue) { addValueToApply(rollup.RecordTypeWithRollup, linkedIdBefore.Value, rollup.RollupField, null, rollup); } if (processPostReferenced && linkedIdAfter.HasValue) { addValueToApply(rollup.RecordTypeWithRollup, linkedIdAfter.Value, rollup.RollupField, null, rollup); } } } } //apply all required changes to parents we captured //type -> ids -> fields . values foreach (var item in dictionaryForDifferences) { var targetType = item.Key; foreach (var idUpdates in item.Value) { var id = idUpdates.Key; //lock the parent record then retreive it plugin.XrmService.SetField(targetType, id, "modifiedon", DateTime.UtcNow); var fieldsForUpdating = idUpdates.Value.Select(kv => kv.FieldName).ToArray(); var targetRecord = plugin.XrmService.Retrieve(targetType, id, idUpdates.Value.Select(kv => kv.FieldName)); //update the fields foreach (var fieldUpdate in idUpdates.Value) { if (AllowsDifferenceChange(fieldUpdate.Rollup.RollupType)) { targetRecord.SetField(fieldUpdate.FieldName, XrmEntity.SumFields(new[] { fieldUpdate.DifferenceValue, targetRecord.GetField(fieldUpdate.FieldName) })); } else { targetRecord.SetField(fieldUpdate.FieldName, GetRollup(fieldUpdate.Rollup, id)); } } plugin.XrmService.Update(targetRecord, fieldsForUpdating); } } } }
private bool DoRefresh() { var target = XrmService.Retrieve(TargetType, TargetId); var fieldsToSet = new List <string>(); if (!target.GetBoolean(Fields.jmcg_calculatedfield_.jmcg_isrecalculating)) { target.SetField(Fields.jmcg_calculatedfield_.jmcg_isrecalculating, true); fieldsToSet.Add(Fields.jmcg_calculatedfield_.jmcg_isrecalculating); } if (!string.IsNullOrWhiteSpace(target.GetStringField(Fields.jmcg_calculatedfield_.jmcg_errorrecalculating))) { target.SetField(Fields.jmcg_calculatedfield_.jmcg_errorrecalculating, null); fieldsToSet.Add(Fields.jmcg_calculatedfield_.jmcg_errorrecalculating); } if (fieldsToSet.Any()) { XrmService.Update(target, fieldsToSet.ToArray()); } var isFinished = false; var createDateThreshold = target.GetDateTimeField(Fields.jmcg_calculatedfield_.jmcg_lastrecalculationcreatedate) ?? new DateTime(1910, 1, 1); try { var config = CalculatedService.LoadCalculatedFieldConfig(target); var startedAt = DateTime.UtcNow; var fieldsToLoad = new List <string>(); var calculatedFieldFieldName = target.GetStringField(Fields.jmcg_calculatedfield_.jmcg_field); var calciulatedFieldEntityName = target.GetStringField(Fields.jmcg_calculatedfield_.jmcg_entitytype); fieldsToLoad.Add(calculatedFieldFieldName); fieldsToLoad.Add("createdon"); fieldsToLoad.AddRange(CalculatedService.GetDependencyFields(config, config.CalculatedFieldEntity.GetStringField(Fields.jmcg_calculatedfield_.jmcg_entitytype))); fieldsToLoad.AddRange(XrmEntity.GetFieldsInFilter(config.ApplyFilterExpression)); while (true) { if (IsSandboxIsolated && DateTime.UtcNow - startedAt > new TimeSpan(0, 0, MaxSandboxIsolationExecutionSeconds - 10)) { break; } else { var processSetQuery = new QueryExpression(calciulatedFieldEntityName); processSetQuery.ColumnSet = new ColumnSet(fieldsToLoad.Distinct().ToArray()); processSetQuery.AddOrder("createdon", OrderType.Ascending); processSetQuery.Criteria.AddCondition(new ConditionExpression("createdon", ConditionOperator.GreaterEqual, createDateThreshold)); var processSet = XrmService.RetrieveMultiple(processSetQuery).Entities.ToList(); var countThisSet = processSet.Count; if (countThisSet == 0) { isFinished = true; } else { var countProcessed = 0; ProcessWhileInSandboxLimit(startedAt, processSet.ToArray(), (entity) => { try { if (target.GetOptionSetValue(Fields.jmcg_calculatedfield_.jmcg_type) == OptionSets.CalculatedField.Type.Rollup) { var rollup = CalculatedService.CreateRollup(config); var rollupService = new CalculatedRollupService(XrmService, new [] { rollup }); rollupService.RefreshRollup(entity.Id, rollup); } else { if (XrmEntity.MeetsFilter(entity.GetField, config.ApplyFilterExpression)) { var oldValue = entity.GetField(calculatedFieldFieldName); var newValue = CalculatedService.GetNewValue(config, entity.GetField); if (!XrmEntity.FieldsEqual(oldValue, newValue)) { entity.SetField(calculatedFieldFieldName, newValue); XrmService.Update(entity, calculatedFieldFieldName); } } } createDateThreshold = entity.GetDateTimeField("createdon") ?? throw new InvalidPluginExecutionException("Error empty createdon " + entity.Id); processSet.Remove(entity); countProcessed++; } catch (Exception ex) { throw new InvalidPluginExecutionException($"Error refreshing ecountered for ID {entity.Id}. Refresh will be discontinued", ex); } }); if (countThisSet < 5000 && countProcessed == countThisSet) { isFinished = true; } } } if (isFinished) { break; } } } catch (Exception ex) { target.SetField(Fields.jmcg_calculatedfield_.jmcg_errorrecalculating, ex.XrmDisplayString().Left(10000)); target.SetField(Fields.jmcg_calculatedfield_.jmcg_lastrecalculationcreatedate, null); target.SetField(Fields.jmcg_calculatedfield_.jmcg_isrecalculating, false); XrmService.Update(target, Fields.jmcg_calculatedfield_.jmcg_errorrecalculating, Fields.jmcg_calculatedfield_.jmcg_lastrecalculationcreatedate, Fields.jmcg_calculatedfield_.jmcg_isrecalculating); throw ex; } if (isFinished) { target.SetField(Fields.jmcg_calculatedfield_.jmcg_lastrecalculationcreatedate, null); target.SetField(Fields.jmcg_calculatedfield_.jmcg_isrecalculating, false); XrmService.Update(target, Fields.jmcg_calculatedfield_.jmcg_lastrecalculationcreatedate, Fields.jmcg_calculatedfield_.jmcg_isrecalculating); } else { target.SetField(Fields.jmcg_calculatedfield_.jmcg_lastrecalculationcreatedate, createDateThreshold); XrmService.Update(target, Fields.jmcg_calculatedfield_.jmcg_lastrecalculationcreatedate); } return(isFinished); }