C# RulesEngine:ApplyPromozioni()中顺序折扣计算未正确生效
C# ASP.NET Core API中RulesEngine折扣引擎问题排查与修复
需求说明
- 基于C# ASP.NET Core API,使用RulesEngine实现折扣引擎,折扣规则存储在数据库中
- 折扣应用逻辑:
- 若存在排他规则(
Esclusiva=1),仅应用优先级最高的该规则 - 若无排他规则,按优先级升序(
Priorita ASC)应用所有匹配规则 - 多折扣需顺序叠加:如20%+10%需先减20%再减10%,而非直接累加为30%
- 若存在排他规则(
现存问题
- 折扣未顺序应用:如20%和10%的规则仅第一个生效
- 无法正确执行数据库存储的动态条件(如
Receipt.Rows.Any(r => r.Cd_AR == "CPUAMD1")) - RulesEngine有时返回0或错误函数名,而非预期折扣值
相关代码
DiscountHelper折扣计算类
public static class DiscountHelper { public static string ApplySequentialDiscount(string existingDiscount, string newDiscount) { int currentDiscount = string.IsNullOrEmpty(existingDiscount) || !int.TryParse(existingDiscount, out var existing) ? 0 : existing; int discountToApply = string.IsNullOrEmpty(newDiscount) || !int.TryParse(newDiscount, out var newDisc) ? 0 : newDisc; // 顺序折扣计算公式:最终折扣 = 当前折扣 + 新折扣 - (当前折扣*新折扣)/100 int finalDiscount = currentDiscount + discountToApply - (currentDiscount * discountToApply / 100); return finalDiscount.ToString(); } }
ApplyDiscountRule自定义动作类
public class ApplyDiscountRule : ActionBase { private readonly Receipt _receipt; private readonly PromozioneRuleDto _rule; public ApplyDiscountRule(Receipt receipt, PromozioneRuleDto rule) { _receipt = receipt ?? throw new ArgumentNullException(nameof(receipt)); _rule = rule ?? throw new ArgumentNullException(nameof(rule)); } public override async ValueTask<object> Run(ActionContext context, RuleParameter[] ruleParameters) { foreach (var row in _receipt.Rows) { if (!EvaluateCondition(_rule.Condizione, row)) { continue; } row.ScontoP = DiscountHelper.ApplySequentialDiscount(row.ScontoP, _rule.Azione); Console.WriteLine($"✅ Discount Applied: {row.Cd_AR} -> {row.ScontoP}"); } return _receipt; } private bool EvaluateCondition(string condition, ReceiptRowDto row) { // 仅做简单字符串匹配,无法真正解析动态表达式 return condition.Contains($"r.Cd_AR == \"{row.Cd_AR}\""); } }
API控制器折扣应用接口
[HttpPost("apply")] public async Task<IActionResult> ApplyPromozioni(Receipt receipt) { var regole = await GetRegoleAttive(); if (!regole.Any()) { return Ok(receipt); } // 处理排他规则 var regoleEsclusive = regole.Where(r => r.Esclusiva).OrderBy(r => r.Priorita).ToList(); if (regoleEsclusive.Any()) { regole = new List<PromozioneRuleDto> { regoleEsclusive.First() }; } else { regole = regole.OrderBy(r => r.Priorita).ToList(); } // 初始化RulesEngine设置 var reSettings = new ReSettings { CustomActions = new Dictionary<string, Func<ActionBase>>() }; // 注册自定义动作 foreach (var rule in regole) { reSettings.CustomActions[rule.Azione] = () => new ApplyDiscountRule(receipt, rule); } // 创建工作流 var engine = new RulesEngine.RulesEngine(reSettings); engine.AddOrUpdateWorkflow(new Workflow[] { new Workflow { WorkflowName = "PromozioniWorkflow", Rules = regole.Select(rule => new Rule { RuleName = rule.Nome, Expression = rule.Condizione, // 数据库存储的动态条件 Actions = new RuleActions { OnSuccess = new ActionInfo { Name = rule.Azione, Context = new Dictionary<string, object> { { "Receipt", receipt } } } } }).ToList() } }); // 执行规则 Console.WriteLine("Executing workflow: PromozioniWorkflow"); var result = await engine.ExecuteAllRulesAsync("PromozioniWorkflow", new { Receipt = receipt }); // 更新结果 foreach (var res in result) { if (res.IsSuccess) { receipt = res.ActionResult.Output as Receipt; Console.WriteLine($"✅ Promotion Applied: {res.Rule.RuleName}"); } } return Ok(receipt); }
预期输入输出
预期输入
{ "Rows": [ { "Cd_AR": "CPUAMD1", "ScontoP": "" }, { "Cd_AR": "DBTEST_ASSEMBLAGGIO", "ScontoP": "" } ] }
预期输出
{ "Rows": [ { "Cd_AR": "CPUAMD1", "ScontoP": "30" }, // 顺序应用20% + 10% { "Cd_AR": "DBTEST_ASSEMBLAGGIO", "ScontoP": "10" } ] }
问题修复方案
1. 折扣未顺序应用的修复
当前代码存在两个核心问题:自定义动作绑定了初始receipt实例,且RulesEngine默认并行执行规则,导致后续规则无法获取已更新的折扣值。
修复步骤:
- 禁用RulesEngine并行执行,保证规则按优先级顺序执行
- 从动作上下文获取最新的
receipt实例,而非构造时绑定
修改ReSettings初始化:
var reSettings = new ReSettings { CustomActions = new Dictionary<string, Func<ActionBase>>(), EnableParallelExecution = false // 禁用并行,强制顺序执行 };
重构ApplyDiscountRule类:
public class ApplyDiscountRule : ActionBase { private readonly PromozioneRuleDto _rule; public ApplyDiscountRule(PromozioneRuleDto rule) { _rule = rule ?? throw new ArgumentNullException(nameof(rule)); } public override async ValueTask<object> Run(ActionContext context, RuleParameter[] ruleParameters) { // 从上下文获取最新的Receipt实例 var receipt = context.GetContextData<Receipt>("Receipt"); if (receipt == null) return null; foreach (var row in receipt.Rows) { if (!EvaluateCondition(_rule.Condizione, receipt, row)) { continue; } row.ScontoP = DiscountHelper.ApplySequentialDiscount(row.ScontoP, _rule.Azione); Console.WriteLine($"✅ Discount Applied: {row.Cd_AR} -> {row.ScontoP}"); } return receipt; } }
2. 动态条件无法正确执行的修复
当前EvaluateCondition仅做字符串匹配,无法解析数据库中的LINQ表达式。需使用System.Linq.Dynamic.Core库实现动态表达式评估。
修复步骤:
- 安装
System.Linq.Dynamic.CoreNuGet包 - 重构
EvaluateCondition方法:
using System.Linq.Dynamic.Core; using System.Linq.Expressions; private bool EvaluateCondition(string condition, Receipt receipt, ReceiptRowDto row) { // 定义表达式参数:Receipt实例和当前行 var parameters = new[] { Expression.Parameter(typeof(Receipt), "Receipt"), Expression.Parameter(typeof(ReceiptRowDto), "r") }; // 解析并编译动态表达式 var lambda = DynamicExpressionParser.ParseLambda(parameters, typeof(bool), condition); return (bool)lambda.Compile().DynamicInvoke(receipt, row); }
3. RulesEngine返回0或错误函数名的修复
当前用折扣值(rule.Azione)作为自定义动作名称,会导致动作名称重复或无效,RulesEngine无法匹配到对应动作。
修复步骤:
- 使用规则唯一标识作为动作名称,避免冲突
- 调整动作注册与规则绑定逻辑:
修改动作注册代码:
foreach (var rule in regole) { // 使用规则ID生成唯一动作名称 var actionName = $"ApplyDiscount_{rule.Id}"; reSettings.CustomActions[actionName] = () => new ApplyDiscountRule(rule); }
修改工作流规则的动作配置:
Rules = regole.Select(rule => new Rule { RuleName = rule.Nome, Expression = rule.Condizione, Actions = new RuleActions { OnSuccess = new ActionInfo { Name = $"ApplyDiscount_{rule.Id}", // 使用唯一动作名称 Context = new Dictionary<string, object> { { "Receipt", receipt } } } } }).ToList()
内容的提问来源于stack exchange,提问作者moongazing
相关产品推荐
相关产品推荐

