diff --git a/src/RulesEngine/Actions/EvaluateRuleAction.cs b/src/RulesEngine/Actions/EvaluateRuleAction.cs index 032308e8..72a9c936 100644 --- a/src/RulesEngine/Actions/EvaluateRuleAction.cs +++ b/src/RulesEngine/Actions/EvaluateRuleAction.cs @@ -28,8 +28,28 @@ internal async override ValueTask ExecuteAndReturnResultAsync( List resultList = null; if (includeRuleResults) { - resultList = new List(output?.Results ?? new List() { }); - resultList.AddRange(innerResult.Results); + // Avoid exponential copying by only including the immediate results from this execution + // The chained rule results are already included in output?.Results + resultList = new List(); + + // Add the chained rule results (from the EvaluateRule action execution) + if (output?.Results != null) + { + resultList.AddRange(output.Results); + } + + // Add the parent rule that triggered this chain (but avoid duplicating it) + if (innerResult.Results != null) + { + foreach (var result in innerResult.Results) + { + // Only add if it's not already in the output results to avoid duplication + if (output?.Results == null || !output.Results.Any(r => ReferenceEquals(r, result))) + { + resultList.Add(result); + } + } + } } return new ActionRuleResult { Output = output?.Output, diff --git a/src/RulesEngine/RulesEngine.cs b/src/RulesEngine/RulesEngine.cs index f50a26f0..da616c74 100644 --- a/src/RulesEngine/RulesEngine.cs +++ b/src/RulesEngine/RulesEngine.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using FluentValidation; @@ -128,11 +128,33 @@ private async ValueTask ExecuteActionAsync(IEnumerable ruleResul public async ValueTask ExecuteActionWorkflowAsync(string workflowName, string ruleName, RuleParameter[] ruleParameters) { - var compiledRule = CompileRule(workflowName, ruleName, ruleParameters); + var compiledRule = GetCompiledRule(workflowName, ruleName, ruleParameters); var resultTree = compiledRule(ruleParameters); return await ExecuteActionForRuleResult(resultTree, true); } + private RuleFunc GetCompiledRule(string workflowName, string ruleName, RuleParameter[] ruleParameters) + { + // Ensure the workflow is registered and rules are compiled + if (!RegisterRule(workflowName, ruleParameters)) + { + throw new ArgumentException($"Rule config file is not present for the {workflowName} workflow"); + } + + // Get the compiled rule from cache + var compiledRulesCacheKey = GetCompiledRulesKey(workflowName, ruleParameters); + var compiledRules = _rulesCache.GetCompiledRules(compiledRulesCacheKey); + + if (compiledRules?.TryGetValue(ruleName, out var compiledRule) == true) + { + return compiledRule; + } + + // Fallback to individual compilation if not found in cache + // This should rarely happen, but provides safety + return CompileRule(workflowName, ruleName, ruleParameters); + } + private async ValueTask ExecuteActionForRuleResult(RuleResultTree resultTree, bool includeRuleResults = false) { var ruleActions = resultTree?.Rule?.Actions;