You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的逻辑覆盖了所有规则,导致执行混乱。

修改步骤

  1. 拆分规则处理逻辑:将ApplyDiscountRule改为仅处理单个传入的规则,而非重新从数据库获取所有规则;
  2. 控制器中独立处理每个规则:确保每个规则的条件判断和动作执行完全独立,互不干扰;
  3. 添加折扣应用标记:在商品行添加标记,确保每个商品仅被同类型规则折扣一次。

修改后的代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 11:22:32