ASP.NET Core Rules Engine问题:首个规则失败后其余规则未执行
问题:RulesEngine中促销规则无法全部独立执行的解决方案
问题背景
在ASP.NET Core中使用RulesEngine库(RulesEngine.Models)开发促销系统时,遇到核心问题:首个规则验证失败后,剩余规则不会被评估。
期望实现的功能
- 从数据库获取有效的促销规则;
- 独立评估每个规则(即使某条规则失败,其余规则仍能运行);
- 规则条件不满足则跳过,继续评估下一条;
- 确保每个商品仅应用一次折扣(避免重复计算)。
问题详情
目前从数据库获取规则后逐个执行,但如果第一条规则条件不成立,后续规则完全不会被检查。例如:
"Receipt.Rows.Sum(r => r.Qta) > 10" "Receipt.Rows.Any(r => r.Cd_AR == \"1\")"
若第一个条件不成立,第二个条件永远不会被触发,需要所有规则都被独立评估,不受其他规则失败影响。
当前核心代码
控制器ApplyPromozioni方法
[HttpPost("apply")] public async Task<IActionResult> ApplyPromozioni(Receipt receipt) { RuleEvaluator.SetReceipt(receipt, clusterARRepository); var regole = await GetRegoleAttive(); if (!regole.Any()) return Ok(receipt); var orderedRules = regole.OrderBy(r => r.Priorita).ToList(); Console.WriteLine($"[DEBUG] Total Rules to Evaluate: {orderedRules.Count}"); var reSettings = new ReSettings { CustomActions = new Dictionary<string, Func<ActionBase>>(), IgnoreException = true, }; foreach (var rule in orderedRules) { if (!reSettings.CustomActions.ContainsKey(rule.Azione)) { reSettings.CustomActions[rule.Azione] = () => CreateActionInstance(rule.Azione, receipt); } } var engine = new RulesEngine.RulesEngine(reSettings); foreach (var rule in orderedRules) { try { Console.WriteLine($"[DEBUG] Evaluating Rule: {rule.Condizione}"); bool conditionMet = RuleEvaluator.EvaluateGeneralCondition(rule.Condizione); Console.WriteLine($"[DEBUG] Rule Result: {rule.Condizione} => {conditionMet}"); if (!conditionMet) { Console.WriteLine($"⚠️ [DEBUG] Rule {rule.Condizione} FAILED, skipping..."); continue; } var workflow = new Workflow { WorkflowName = $"Workflow_{rule.Nome}", Rules = new List<Rule> { new Rule { RuleName = rule.Nome, Expression = "true", // 🔥 **Forces the action to always run** Actions = new RuleActions { OnSuccess = new ActionInfo { Name = rule.Azione, Context = new Dictionary<string, object> { { "Receipt", receipt } } } } } } }; engine.AddOrUpdateWorkflow(new[] { workflow }); var result = await engine.ExecuteAllRulesAsync(workflow.WorkflowName, new { Receipt = receipt }); foreach (var res in result) { Console.WriteLine($"✅ {res.Rule.RuleName} - Success: {res.IsSuccess}"); if (res.IsSuccess) { receipt = res.ActionResult.Output as Receipt; Console.WriteLine($"✅ Applied Rule: {res.Rule.RuleName}"); } else { Console.WriteLine($"⚠️ Rule '{res.Rule.RuleName}' did not apply. Continuing to next rule..."); } } } catch (Exception ex) { Console.WriteLine($"❌ [ERROR] Rule {rule.Nome} failed: {ex.Message}. Skipping this rule and continuing..."); } } Console.WriteLine($"{JsonConvert.SerializeObject(receipt, Formatting.Indented)}"); return Ok(receipt); }
ApplyDiscountRule类
public class ApplyDiscountRule : ActionBase { private readonly Receipt _receipt; private readonly IPromozioneRuleRepository _promozioneRuleRepository; private readonly IClusterARRepository _clusterARRepository; public ApplyDiscountRule(Receipt receipt, IPromozioneRuleRepository promozioneRuleRepository, IClusterARRepository clusterARRepository) { _receipt = receipt; _promozioneRuleRepository = promozioneRuleRepository; _clusterARRepository = clusterARRepository; } public override async ValueTask<object> Run(ActionContext context, RuleParameter[] ruleParameters) { // **从数据库获取规则** var rulesFromDb = (await _promozioneRuleRepository.GetActivePromotionRulesAsync()) .OrderBy(r => r.Priorita) // 按优先级排序 .ToList(); if (!rulesFromDb.Any()) { Console.WriteLine($"⚠️ [WARNING] No active promotion rules found in DB."); return _receipt; } RuleEvaluator.SetReceipt(_receipt, _clusterARRepository); var generalRules = rulesFromDb .Where(rule => !rule.Condizione.Contains("Receipt.Rows.Any(r => r.Cd_AR ==")) // 不含Cluster ID的规则 .ToList(); foreach (var rule in generalRules) { if (RuleEvaluator.EvaluateGeneralCondition(rule.Condizione)) // 仅评估通用条件 { Console.WriteLine($"✅ [DEBUG] General Rule Applied: {rule.Condizione}"); foreach (var row in _receipt.Rows) { string discountValue = DiscountHelper.ExtractDiscountValue(rule.Azione); row.ScontoP = DiscountHelper.ApplySequentialDiscount(row.ScontoP ?? "0", discountValue); Console.WriteLine($"✅ [DEBUG] Applied General Discount {discountValue}% to {row.Cd_AR}"); } } } foreach (var row in _receipt.Rows) { Console.WriteLine($"🔍 [DEBUG] Checking row: {row.Cd_AR}, Qta: {row.Qta}"); var matchingRules = rulesFromDb .Where(r => r.Condizione.Contains("Receipt.Rows.Any(r => r.Cd_AR ==")) .ToList(); foreach (var rule in matchingRules) { var match = Regex.Match(rule.Condizione, @"Receipt\.Rows\.Any\(r => r\.Cd_AR == \"(\d+)\"\)"); if (!match.Success) { Console.WriteLine($"[DEBUG] Skipping rule, no Cluster ID found: {rule.Condizione}"); continue; } int clusterId = int.Parse(match.Groups[1].Value); Console.WriteLine($"[DEBUG] Found Cluster ID: {clusterId} in PromozioneRule"); var clusterData = await _clusterARRepository.GetByIdAsync(clusterId); if (clusterData == null) { Console.WriteLine($"[ERROR] No ClusterAR found for Cluster ID: {clusterId}"); continue; } string clusterCdAR = clusterData.Cd_AR; Console.WriteLine($"[DEBUG] Found Cd_AR for Cluster {clusterId}: {clusterCdAR}"); if (row.Cd_AR != clusterCdAR) { Console.WriteLine($"[DEBUG] Skipping row {row.Cd_AR}, not matching ClusterAR Cd_AR."); continue; } if (row.Qta >= clusterData.Quantity) { string discountValue = DiscountHelper.ExtractDiscountValue(rule.Azione); row.ScontoP = DiscountHelper.ApplySequentialDiscount(row.ScontoP ?? "0", discountValue); Console.WriteLine($"✅ [DEBUG] Applied Discount {discountValue}% to {row.Cd_AR}"); } else { Console.WriteLine($"❌ [DEBUG] {row.Cd_AR} does not meet quantity requirement for Cluster {clusterId}. Skipping."); } } } return _receipt; } }
尝试过但无效的方法
- 强制规则运行:设置
Expression = "true",但首个规则失败后后续规则仍无法执行; - 启用
IgnoreException = true:仅防止错误中断,无法保证规则独立运行; - 手动循环规则:逐个循环时,首个规则失败后
ApplyDiscountRule不会处理后续规则。
解决方案
问题根源在于**ApplyDiscountRule内部重复获取并处理所有规则**,导致控制器中循环规则时,每次执行ApplyDiscountRule都会重新处理全部规则,而非当前单个规则。同时控制器的规则条件判断后,仅对满足条件的规则执行动作,但ApplyDiscountRule的逻辑覆盖了所有规则,导致执行混乱。
修改步骤
- 拆分规则处理逻辑:将
ApplyDiscountRule改为仅处理单个传入的规则,而非重新从数据库获取所有规则; - 控制器中独立处理每个规则:确保每个规则的条件判断和动作执行完全独立,互不干扰;
- 添加折扣应用标记:在商品行添加标记,确保每个商品仅被同类型规则折扣一次。
修改后的代码
1. 控制器ApplyPromozioni方法优化
[HttpPost("apply")] public async Task<IActionResult> ApplyPromozioni(Receipt receipt) { RuleEvaluator.SetReceipt(receipt, clusterARRepository); var regole = await GetRegoleAttive(); if (!regole.Any()) return Ok(receipt); var orderedRules = regole.OrderBy(r => r.Priorita).ToList(); Console.WriteLine($"[DEBUG] Total Rules to Evaluate: {orderedRules.Count}"); var reSettings = new ReSettings { IgnoreException = true, }; var engine = new RulesEngine.RulesEngine(reSettings); foreach (var rule in orderedRules) { try { Console.WriteLine($"[DEBUG] Evaluating Rule: {rule.Condizione}"); // 独立评估当前规则的条件 bool conditionMet = RuleEvaluator.EvaluateGeneralCondition(rule.Condizione); Console.WriteLine($"[DEBUG] Rule Result: {rule.Condizione} => {conditionMet}"); if (!conditionMet) { Console.WriteLine($"⚠️ [DEBUG] Rule {rule.Condizione} FAILED, skipping..."); continue; } // 为当前规则创建独立的工作流,传入当前规则作为参数 var workflow = new Workflow { WorkflowName = $"Workflow_{rule.Nome}", Rules = new List<Rule> { new Rule { RuleName = rule.Nome, Expression = "true", Actions = new RuleActions { OnSuccess = new ActionInfo { Name = "ApplySingleDiscountRule", Context = new Dictionary<string, object> { { "Receipt", receipt }, { "CurrentRule", rule } // 传入当前规则 } } } } } }; engine.AddOrUpdateWorkflow(new[] { workflow }); // 执行当前规则的动作 var result = await engine.ExecuteAllRulesAsync(workflow.WorkflowName, new { Receipt = receipt, CurrentRule = rule }); foreach (var res in result) { Console.WriteLine($"✅ {res.Rule.RuleName} - Success: {res.IsSuccess}"); if (res.IsSuccess) { receipt = res.ActionResult.Output as Receipt; Console.WriteLine($"✅ Applied Rule: {res.Rule.RuleName}"); } } } catch (Exception ex) { Console.WriteLine($"❌ [ERROR] Rule {rule.Nome} failed: {ex.Message}. Skipping this rule and continuing..."); // 捕获异常后继续循环,不影响后续规则 continue; } } Console.WriteLine($"{JsonConvert.SerializeObject(receipt, Formatting.Indented)}"); return Ok(receipt); } // 创建单个规则处理的动作实例 private ActionBase CreateSingleRuleActionInstance(Receipt receipt, PromozioneRule currentRule) { return new ApplySingleDiscountRule(receipt, currentRule, clusterARRepository); }
2. 新的ApplySingleDiscountRule类(替代原ApplyDiscountRule)
public class ApplySingleDiscountRule : ActionBase { private readonly Receipt _receipt; private readonly PromozioneRule _currentRule; private readonly IClusterARRepository _clusterARRepository; public ApplySingleDiscountRule(Receipt receipt, PromozioneRule currentRule, IClusterARRepository clusterARRepository) { _receipt = receipt; _currentRule = currentRule; _clusterARRepository = clusterARRepository; } public override async ValueTask<object> Run(ActionContext context, RuleParameter[] ruleParameters) { string discountValue = DiscountHelper.ExtractDiscountValue(_currentRule.Azione); bool isClusterRule = _currentRule.Condizione.Contains("Receipt.Rows.Any(r => r.Cd_AR =="); if (!isClusterRule) { // 处理通用规则:仅对未应用过通用折扣的商品行生效 foreach (var row in _receipt.Rows.Where(r => !r.HasAppliedGeneralDiscount)) { row.ScontoP = DiscountHelper.ApplySequentialDiscount(row.ScontoP ?? "0", discountValue); row.HasAppliedGeneralDiscount = true; // 标记已应用通用折扣 Console.WriteLine($"✅ [DEBUG] Applied General Discount {discountValue}% to {row.Cd_AR}"); } } else { // 处理Cluster规则:解析Cluster ID,匹配商品行并应用折扣(仅一次) var match = Regex.Match(_currentRule.Condizione, @"Receipt\.Rows\.Any\(r => r\.Cd_AR == \"(\d+)\"\)"); if (!match.Success) { Console.WriteLine($"[DEBUG] Skipping invalid cluster rule: {_currentRule.Condizione}"); return _receipt; } int clusterId = int.Parse(match.Groups[1].Value); var clusterData = await _clusterARRepository.GetByIdAsync(clusterId); if (clusterData == null) { Console.WriteLine($"[ERROR] Cluster {clusterId} not found"); return _receipt; } string clusterCdAR = clusterData.Cd_AR; // 仅对未应用过该Cluster折扣的商品行生效 foreach (var row in _receipt.Rows.Where(r => r.Cd_AR == clusterCdAR && !r.AppliedClusterDiscounts.Contains(clusterId))) { if (row.Qta >= clusterData.Quantity) { row.ScontoP = DiscountHelper.ApplySequentialDiscount(row.ScontoP ?? "0", discountValue); row.AppliedClusterDiscounts.Add(clusterId); // 标记已应用该Cluster折扣 Console.WriteLine($"✅ [DEBUG] Applied Cluster {clusterId} Discount {discountValue}% to {row.Cd_AR}"); } else { Console.WriteLine($"❌ [DEBUG] {row.Cd_AR} Qta {row.Qta} < {clusterData.Quantity}, skip discount"); } } } return _receipt; } }
3. 修改ReceiptRow类添加折扣标记
public class ReceiptRow { // 原有属性 public string Cd_AR { get; set; } public int Qta { get; set; } public string ScontoP { get; set; } // 添加折扣应用标记 public bool HasAppliedGeneralDiscount { get; set; } = false; public List<int> AppliedClusterDiscounts { get; set; } = new List<int>(); }
关键改进点
- 规则独立执行:每个规则在控制器中被单独评估、单独创建工作流、单独执行动作,完全互不干扰;
- 避免重复处理:
ApplySingleDiscountRule仅处理当前传入的规则,不再重复获取数据库所有规则; - 防止重复折扣:通过
HasAppliedGeneralDiscount和AppliedClusterDiscounts标记,确保每个商品行仅被同类型规则折扣一次; - 异常隔离:每个规则的执行都被try-catch包裹,单个规则抛出异常时不会中断整个流程。
内容的提问来源于stack exchange,提问作者moongazing
相关产品推荐
相关产品推荐

