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

C# RulesEngine:ApplyPromozioni()中顺序折扣计算未正确生效

C# ASP.NET Core API中RulesEngine折扣引擎问题排查与修复

需求说明

  • 基于C# ASP.NET Core API,使用RulesEngine实现折扣引擎,折扣规则存储在数据库中
  • 折扣应用逻辑:
    • 若存在排他规则(Esclusiva=1),仅应用优先级最高的该规则
    • 若无排他规则,按优先级升序(Priorita ASC)应用所有匹配规则
    • 多折扣需顺序叠加:如20%+10%需先减20%再减10%,而非直接累加为30%

现存问题

  1. 折扣未顺序应用:如20%和10%的规则仅第一个生效
  2. 无法正确执行数据库存储的动态条件(如Receipt.Rows.Any(r => r.Cd_AR == "CPUAMD1"))
  3. 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库实现动态表达式评估。

修复步骤:

  1. 安装System.Linq.Dynamic.Core NuGet包
  2. 重构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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 14:47:33