【问题标题】:How to implement a rule engine?如何实现规则引擎?
【发布时间】:2011-09-23 04:49:41
【问题描述】:

我有一个存储以下内容的数据库表:

RuleID  objectProperty ComparisonOperator  TargetValue
1       age            'greater_than'             15
2       username       'equal'             'some_name'
3       tags           'hasAtLeastOne'     'some_tag some_tag2'

现在说我有这些规则的集合:

List<Rule> rules = db.GetRules();

现在我也有一个用户实例:

User user = db.GetUser(....);

我将如何遍历这些规则,并应用逻辑并执行比较等?

if(user.age > 15)

if(user.username == "some_name")

由于对象的“年龄”或“用户名”等属性与比较运算符“great_than”和“等于”一起存储在表中,我怎么可能做到这一点?

C# 是一种静态类型的语言,所以不知道如何前进。

【问题讨论】:

    标签: c# dynamic rule-engine


    【解决方案1】:

    这个sn-p 将规则编译成快速的可执行代码(使用Expression trees)并且不需要任何复杂的switch语句:

    (编辑:full working example with generic method

    public Func<User, bool> CompileRule(Rule r)
    {
        var paramUser = Expression.Parameter(typeof(User));
        Expression expr = BuildExpr(r, paramUser);
        // build a lambda function User->bool and compile it
        return Expression.Lambda<Func<User, bool>>(expr, paramUser).Compile();
    }
    

    然后你可以写:

    List<Rule> rules = new List<Rule> {
        new Rule ("Age", "GreaterThan", "21"),
        new Rule ( "Name", "Equal", "John"),
        new Rule ( "Tags", "Contains", "C#" )
    };
    
    // compile the rules once
    var compiledRules = rules.Select(r => CompileRule(r)).ToList();
    
    public bool MatchesAllRules(User user)
    {
        return compiledRules.All(rule => rule(user));
    }
    

    这里是 BuildExpr 的实现:

    Expression BuildExpr(Rule r, ParameterExpression param)
    {
        var left = MemberExpression.Property(param, r.MemberName);
        var tProp = typeof(User).GetProperty(r.MemberName).PropertyType;
        ExpressionType tBinary;
        // is the operator a known .NET operator?
        if (ExpressionType.TryParse(r.Operator, out tBinary)) {
            var right = Expression.Constant(Convert.ChangeType(r.TargetValue, tProp));
            // use a binary operation, e.g. 'Equal' -> 'u.Age == 21'
            return Expression.MakeBinary(tBinary, left, right);
        } else {
            var method = tProp.GetMethod(r.Operator);
            var tParam = method.GetParameters()[0].ParameterType;
            var right = Expression.Constant(Convert.ChangeType(r.TargetValue, tParam));
            // use a method call, e.g. 'Contains' -> 'u.Tags.Contains(some_tag)'
            return Expression.Call(left, method, right);
        }
    }
    

    请注意,我使用 'GreaterThan' 而不是 'greater_than' 等 - 这是因为 'GreaterThan' 是运算符的 .NET 名称,因此我们不需要任何额外的映射。

    如果您需要自定义名称,您可以构建一个非常简单的字典,并在编译规则之前翻译所有运算符:

    var nameMap = new Dictionary<string, string> {
        { "greater_than", "GreaterThan" },
        { "hasAtLeastOne", "Contains" }
    };
    

    为了简单起见,代码使用 User 类型。您可以将 User 替换为泛型类型 T 以获得任何类型的对象的 generic Rule compiler。此外,代码应该处理错误,例如未知的操作员名称。

    请注意,甚至在引入表达式树 API 之前,使用 Reflection.Emit 就可以动态生成代码。 LambdaExpression.Compile() 方法在幕后使用了 Reflection.Emit(您可以使用 ILSpy 看到这一点)。

    【讨论】:

    • 我在哪里可以阅读更多关于你的答案来学习类/对象/等。你在你的代码中有吗?主要是表达式树?
    • 所有类都来自命名空间 System.Linq.Expressions,并且都是使用 Expression 类的工厂方法创建的 - 类型为“Expression”。在您的 IDE 中访问所有这些。在此处阅读有关表达式树的更多信息msdn.microsoft.com/en-us/library/bb397951.aspx
    • @Martin 在哪里可以找到合格的 .NET 操作员名称列表?
    • @Dark Slipstream 你可以在这里找到它们 msdn.microsoft.com/en-us/library/bb361179.aspx。并非所有这些都是布尔表达式 - 仅使用布尔表达式(例如 GreaterThan、NotEqual 等)。
    • @BillDaugherty 规则一个具有三个属性的简单值类:MemberName、Operator、TargetValue。例如,新规则(“Age”、“GreaterThan”、“20”)。
    【解决方案2】:

    这是一些按原样编译并完成工作的代码。 基本上使用两个字典,一个包含从运算符名称到布尔函数的映射,另一个包含从 User 类型的属性名称到用于调用属性 getter(如果是公共的)的 PropertyInfos 的映射。 您将 User 实例和表中的三个值传递给静态 Apply 方法。

    class User
    {
        public int Age { get; set; }
        public string UserName { get; set; }
    }
    
    class Operator
    {
        private static Dictionary<string, Func<object, object, bool>> s_operators;
        private static Dictionary<string, PropertyInfo> s_properties;
        static Operator()
        {
            s_operators = new Dictionary<string, Func<object, object, bool>>();
            s_operators["greater_than"] = new Func<object, object, bool>(s_opGreaterThan);
            s_operators["equal"] = new Func<object, object, bool>(s_opEqual);
    
            s_properties = typeof(User).GetProperties().ToDictionary(propInfo => propInfo.Name);
        }
    
        public static bool Apply(User user, string op, string prop, object target)
        {
            return s_operators[op](GetPropValue(user, prop), target);
        }
    
        private static object GetPropValue(User user, string prop)
        {
            PropertyInfo propInfo = s_properties[prop];
            return propInfo.GetGetMethod(false).Invoke(user, null);
        }
    
        #region Operators
    
        static bool s_opGreaterThan(object o1, object o2)
        {
            if (o1 == null || o2 == null || o1.GetType() != o2.GetType() || !(o1 is IComparable))
                return false;
            return (o1 as IComparable).CompareTo(o2) > 0;
        }
    
        static bool s_opEqual(object o1, object o2)
        {
            return o1 == o2;
        }
    
        //etc.
    
        #endregion
    
        public static void Main(string[] args)
        {
            User user = new User() { Age = 16, UserName = "John" };
            Console.WriteLine(Operator.Apply(user, "greater_than", "Age", 15));
            Console.WriteLine(Operator.Apply(user, "greater_than", "Age", 17));
            Console.WriteLine(Operator.Apply(user, "equal", "UserName", "John"));
            Console.WriteLine(Operator.Apply(user, "equal", "UserName", "Bob"));
        }
    }
    

    【讨论】:

      【解决方案3】:

      我构建了一个规则引擎,它采用的方法与您在问题中概述的方法不同,但我认为您会发现它比您当前的方法灵活得多。

      您当前的方法似乎专注于单个实体“用户”,并且您的持久规则识别“属性名”、“操作员”和“值”。我的模式,而是将谓词 (Func) 的 C# 代码存储在我数据库的“表达式”列中。在当前的设计中,我使用代码生成从我的数据库中查询“规则”并用“规则”类型编译一个程序集,每个类型都有一个“测试”方法。这是每个规则实现的接口的签名:

      public interface IDataRule<TEntity> 
      {
          /// <summary>
          /// Evaluates the validity of a rule given an instance of an entity
          /// </summary>
          /// <param name="entity">Entity to evaluate</param>
          /// <returns>result of the evaluation</returns>
          bool Test(TEntity entity);
          /// <summary>
          /// The unique indentifier for a rule.
          /// </summary>
           int RuleId { get; set; }
          /// <summary>
          /// Common name of the rule, not unique
          /// </summary>
           string RuleName { get; set; }
          /// <summary>
          /// Indicates the message used to notify the user if the rule fails
          /// </summary>
           string ValidationMessage { get; set; }   
           /// <summary>
           /// indicator of whether the rule is enabled or not
           /// </summary>
           bool IsEnabled { get; set; }
          /// <summary>
          /// Represents the order in which a rule should be executed relative to other rules
          /// </summary>
           int SortOrder { get; set; }
      }
      

      当应用程序第一次执行时,“表达式”被编译为“测试”方法的主体。如您所见,表中的其他列也作为规则中的第一类属性显示,因此开发人员可以灵活地创建用户如何收到失败或成功通知的体验。

      在您的应用程序中生成内存程序集是 1 次,您可以通过在评估规则时不必使用反射来获得性能提升。您的表达式会在运行时检查,因为如果属性名称拼写错误等,程序集将无法正确生成。

      创建内存程序集的机制如下:

      • 从数据库中加载您的规则
      • 遍历规则并为每个规则,使用 StringBuilder 和一些字符串连接编写表示从 IDataRule 继承的类的 Text
      • 使用 CodeDOM 编译 -- more info

      这实际上非常简单,因为对于大多数情况,此代码是构造函数中的属性实现和值初始化。除此之外,唯一的其他代码是表达式。
      注意:由于 CodeDOM 的限制,您的表达式必须是 .NET 2.0(无 lambda 或其他 C# 3.0 功能)。

      这里有一些示例代码。

      sb.AppendLine(string.Format("\tpublic class {0} : SomeCompany.ComponentModel.IDataRule<{1}>", className, typeName));
                  sb.AppendLine("\t{");
                  sb.AppendLine("\t\tprivate int _ruleId = -1;");
                  sb.AppendLine("\t\tprivate string _ruleName = \"\";");
                  sb.AppendLine("\t\tprivate string _ruleType = \"\";");
                  sb.AppendLine("\t\tprivate string _validationMessage = \"\";");
                  /// ... 
                  sb.AppendLine("\t\tprivate bool _isenabled= false;");
                  // constructor
                  sb.AppendLine(string.Format("\t\tpublic {0}()", className));
                  sb.AppendLine("\t\t{");
                  sb.AppendLine(string.Format("\t\t\tRuleId = {0};", ruleId));
                  sb.AppendLine(string.Format("\t\t\tRuleName = \"{0}\";", ruleName.TrimEnd()));
                  sb.AppendLine(string.Format("\t\t\tRuleType = \"{0}\";", ruleType.TrimEnd()));                
                  sb.AppendLine(string.Format("\t\t\tValidationMessage = \"{0}\";", validationMessage.TrimEnd()));
                  // ...
                  sb.AppendLine(string.Format("\t\t\tSortOrder = {0};", sortOrder));                
      
                  sb.AppendLine("\t\t}");
                  // properties
                  sb.AppendLine("\t\tpublic int RuleId { get { return _ruleId; } set { _ruleId = value; } }");
                  sb.AppendLine("\t\tpublic string RuleName { get { return _ruleName; } set { _ruleName = value; } }");
                  sb.AppendLine("\t\tpublic string RuleType { get { return _ruleType; } set { _ruleType = value; } }");
      
                  /// ... more properties -- omitted
      
                  sb.AppendLine(string.Format("\t\tpublic bool Test({0} entity) ", typeName));
                  sb.AppendLine("\t\t{");
                  // #############################################################
                  // NOTE: This is where the expression from the DB Column becomes
                  // the body of the Test Method, such as: return "entity.Prop1 < 5"
                  // #############################################################
                  sb.AppendLine(string.Format("\t\t\treturn {0};", expressionText.TrimEnd()));
                  sb.AppendLine("\t\t}");  // close method
                  sb.AppendLine("\t}"); // close Class
      

      除此之外,我还创建了一个名为“DataRuleCollection”的类,它实现了 ICollection>。这使我能够创建“TestAll”功能和用于按名称执行特定规则的索引器。以下是这两种方法的实现。

          /// <summary>
          /// Indexer which enables accessing rules in the collection by name
          /// </summary>
          /// <param name="ruleName">a rule name</param>
          /// <returns>an instance of a data rule or null if the rule was not found.</returns>
          public IDataRule<TEntity, bool> this[string ruleName]
          {
              get { return Contains(ruleName) ? list[ruleName] : null; }
          }
          // in this case the implementation of the Rules Collection is: 
          // DataRulesCollection<IDataRule<User>> and that generic flows through to the rule.
          // there are also some supporting concepts here not otherwise outlined, such as a "FailedRules" IList
          public bool TestAllRules(User target) 
          {
              rules.FailedRules.Clear();
              var result = true;
      
              foreach (var rule in rules.Where(x => x.IsEnabled)) 
              {
      
                  result = rule.Test(target);
                  if (!result)
                  {
      
                      rules.FailedRules.Add(rule);
                  }
              }
      
              return (rules.FailedRules.Count == 0);
          }
      

      更多代码:有一个与代码生成相关的代码请求。我将功能封装在一个名为“RulesAssemblyGenerator”的类中,我将其包含在下面。

      namespace Xxx.Services.Utils
          {
              public static class RulesAssemblyGenerator
              {
                  static List<string> EntityTypesLoaded = new List<string>();
      
                  public static void Execute(string typeName, string scriptCode)
                  {
                      if (EntityTypesLoaded.Contains(typeName)) { return; } 
                      // only allow the assembly to load once per entityType per execution session
                      Compile(new CSharpCodeProvider(), scriptCode);
                      EntityTypesLoaded.Add(typeName);
                  }
                  private static void Compile(CodeDom.CodeDomProvider provider, string source)
                  {
                      var param = new CodeDom.CompilerParameters()
                      {
                          GenerateExecutable = false,
                          IncludeDebugInformation = false,
                          GenerateInMemory = true
                      };
                      var path = System.Reflection.Assembly.GetExecutingAssembly().Location;
                      var root_Dir = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Bin");
                      param.ReferencedAssemblies.Add(path);
                      // Note: This dependencies list are included as assembly reference and they should list out all dependencies
                      // That you may reference in your Rules or that your entity depends on.
                      // some assembly names were changed... clearly.
                      var dependencies = new string[] { "yyyyyy.dll", "xxxxxx.dll", "NHibernate.dll", "ABC.Helper.Rules.dll" };
                      foreach (var dependency in dependencies)
                      {
                          var assemblypath = System.IO.Path.Combine(root_Dir, dependency);
                          param.ReferencedAssemblies.Add(assemblypath);
                      }
                      // reference .NET basics for C# 2.0 and C#3.0
                      param.ReferencedAssemblies.Add(@"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll");
                      param.ReferencedAssemblies.Add(@"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Core.dll");
                      var compileResults = provider.CompileAssemblyFromSource(param, source);
                      var output = compileResults.Output;
                      if (compileResults.Errors.Count != 0)
                      {
                          CodeDom.CompilerErrorCollection es = compileResults.Errors;
                          var edList = new List<DataRuleLoadExceptionDetails>();
                          foreach (CodeDom.CompilerError s in es)
                              edList.Add(new DataRuleLoadExceptionDetails() { Message = s.ErrorText, LineNumber = s.Line });
                          var rde = new RuleDefinitionException(source, edList.ToArray());
                          throw rde;
                      }
                  }
              }
          }
      

      如果有任何其他问题或要求提供更多代码示例,请告诉我。

      【讨论】:

      • 你说得对,引擎可以变得更通用,CodeDOM API 绝对也是一种选择。也许代替不是很清楚的“sb.AppendLine”代码,您可以展示您如何准确地调用 CodeDOM?
      【解决方案4】:

      反思是您最通用的答案。您有三列数据,需要以不同的方式处理它们:

      1. 您的字段名称。反射是从编码字段名称中获取值的方法。

      2. 您的比较运算符。这些数量应该是有限的,因此案例陈述应该最容易处理它们。尤其是其中一些(有一个或多个)稍微复杂一些。

      3. 您的比较值。如果这些都是直接值,那么这很容易,尽管您需要将多个条目分开。但是,如果它们也是字段名称,您也可以使用反射。

      我会采取类似的方法:

          var value = user.GetType().GetProperty("age").GetValue(user, null);
          //Thank you Rick! Saves me remembering it;
          switch(rule.ComparisonOperator)
              case "equals":
                   return EqualComparison(value, rule.CompareTo)
              case "is_one_or_more_of"
                   return IsInComparison(value, rule.CompareTo)
      

      等等。等等

      它为您提供了添加更多比较选项的灵活性。这也意味着您可以在比较方法中编写您可能想要的任何类型验证,并使它们变得尽可能复杂。这里还有一个选项,可以将 CompareTo 评估为对另一行的递归调用,或者作为字段值,可以这样做:

                   return IsInComparison(value, EvaluateComparison(rule.CompareTo))
      

      这一切都取决于未来的可能性......

      【讨论】:

      • 您可以缓存反射的程序集/对象,这将使您的代码更加高效。
      【解决方案5】:

      如果您只有少数几个属性和运算符,阻力最小的方法是将所有检查编码为特殊情况,如下所示:

      public bool ApplyRules(List<Rule> rules, User user)
      {
          foreach (var rule in rules)
          {
              IComparable value = null;
              object limit = null;
              if (rule.objectProperty == "age")
              {
                  value = user.age;
                  limit = Convert.ToInt32(rule.TargetValue);
              }
              else if (rule.objectProperty == "username")
              {
                  value = user.username;
                  limit = rule.TargetValue;
              }
              else
                  throw new InvalidOperationException("invalid property");
      
              int result = value.CompareTo(limit);
      
              if (rule.ComparisonOperator == "equal")
              {
                  if (!(result == 0)) return false;
              }
              else if (rule.ComparisonOperator == "greater_than")
              {
                  if (!(result > 0)) return false;
              }
              else
                  throw new InvalidOperationException("invalid operator");
          }
          return true;
      }
      

      如果您有很多属性,您可能会发现表驱动的方法更受欢迎。在这种情况下,您将创建一个静态 Dictionary,将属性名称映射到匹配的委托,例如 Func&lt;User, object&gt;

      如果您在编译时不知道属性的名称,或者您想避免每个属性的特殊情况并且不想使用表格方法,您可以使用反射来获取属性。例如:

      var value = user.GetType().GetProperty("age").GetValue(user, null);
      

      但由于TargetValue 可能是string,因此您需要注意在必要时从规则表中进行类型转换。

      【讨论】:

      • value.CompareTo(limit) 返回什么? -1 0 还是 1?没见过那个b4!
      • @Blankman:关闭:小于零、零或大于零。 IComparable 用于比较事物。以下是文档:IComparable.CompareTo Method.
      • 我不明白为什么这个答案被投票赞成。它违反了许多设计原则:“告诉不问”=> 每个规则都应该要求返回一个结果。 “打开以进行扩展/关闭以进行修改” => 任何新规则都意味着 ApplyRules 方法需要修改。加上代码很难看懂。
      • 确实,阻力最小的路径很少是最好的路径。请查看优秀的表达式树答案并点赞。
      【解决方案6】:

      带有扩展方法的面向数据类型的方法怎么样:

      public static class RoleExtension
      {
          public static bool Match(this Role role, object obj )
          {
              var property = obj.GetType().GetProperty(role.objectProperty);
              if (property.PropertyType == typeof(int))
              {
                  return ApplyIntOperation(role, (int)property.GetValue(obj, null));
              }
              if (property.PropertyType == typeof(string))
              {
                  return ApplyStringOperation(role, (string)property.GetValue(obj, null));
              }
              if (property.PropertyType.GetInterface("IEnumerable<string>",false) != null)
              {
                  return ApplyListOperation(role, (IEnumerable<string>)property.GetValue(obj, null));
              }
              throw new InvalidOperationException("Unknown PropertyType");
          }
      
          private static bool ApplyIntOperation(Role role, int value)
          {
              var targetValue = Convert.ToInt32(role.TargetValue);
              switch (role.ComparisonOperator)
              {
                  case "greater_than":
                      return value > targetValue;
                  case "equal":
                      return value == targetValue;
                  //...
                  default:
                      throw new InvalidOperationException("Unknown ComparisonOperator");
              }
          }
      
          private static bool ApplyStringOperation(Role role, string value)
          {
              //...
              throw new InvalidOperationException("Unknown ComparisonOperator");
          }
      
          private static bool ApplyListOperation(Role role, IEnumerable<string> value)
          {
              var targetValues = role.TargetValue.Split(' ');
              switch (role.ComparisonOperator)
              {
                  case "hasAtLeastOne":
                      return value.Any(v => targetValues.Contains(v));
                      //...
              }
              throw new InvalidOperationException("Unknown ComparisonOperator");
          }
      }
      

      你可以这样评价:

      var myResults = users.Where(u => roles.All(r => r.Match(u)));
      

      【讨论】:

        【解决方案7】:

        虽然回答“如何实现规则引擎?(在 C# 中)”问题的最明显方法是按顺序执行给定的一组规则,但这通常被认为是一种幼稚的实现(并不意味着不起作用:-)

        在您的情况下似乎“足够好”,因为您的问题似乎更多是“如何按顺序运行一组规则”,而 lambda/表达式树(Martin 的答案)肯定是最优雅的方式无论您是否配备了最新的 C# 版本。

        但是,对于更高级的场景,这里有一个指向实际上在许多商业规则引擎系统中实现的 Rete Algorithm 的链接,以及指向 NRuler 的另一个链接,该算法在 C# 中的实现。

        【讨论】:

          【解决方案8】:

          Martin 的回答非常好。我实际上制作了一个与他的想法相同的规则引擎。我很惊讶它几乎是一样的。我已经包含了他的一些代码以对其进行一些改进。虽然我已经让它处理更复杂的规则。

          你可以看Yare.NET

          或在Nuget下载

          【讨论】:

            【解决方案9】:

            使用工作流规则引擎怎么样?

            您可以在没有工作流的情况下执行 Windows 工作流规则 见 Guy Burstein 的博客:http://blogs.microsoft.co.il/blogs/bursteg/archive/2006/10/11/RuleExecutionWithoutWorkflow.aspx

            要以编程方式创建规则,请参阅 Stephen Kaufman 的 WebLog

            http://blogs.msdn.com/b/skaufman/archive/2006/05/15/programmatically-create-windows-workflow-rules.aspx

            【讨论】:

              【解决方案10】:

              我在规则之间添加了 and,or 的实现 我添加了类 RuleExpression 表示树的根,它可以是简单的规则,也可以是和,或二进制表达式,因为它们没有规则并且有表达式:

              public class RuleExpression
              {
                  public NodeOperator NodeOperator { get; set; }
                  public List<RuleExpression> Expressions { get; set; }
                  public Rule Rule { get; set; }
              
                  public RuleExpression()
                  {
              
                  }
                  public RuleExpression(Rule rule)
                  {
                      NodeOperator = NodeOperator.Leaf;
                      Rule = rule;
                  }
              
                  public RuleExpression(NodeOperator nodeOperator, List<RuleExpression> expressions, Rule rule)
                  {
                      this.NodeOperator = nodeOperator;
                      this.Expressions = expressions;
                      this.Rule = rule;
                  }
              }
              
              
              public enum NodeOperator
              {
                  And,
                  Or,
                  Leaf
              }
              

              我有另一个类将 ruleExpression 编译为一个 Func&lt;T, bool&gt;:

               public static Func<T, bool> CompileRuleExpression<T>(RuleExpression ruleExpression)
                  {
                      //Input parameter
                      var genericType = Expression.Parameter(typeof(T));
                      var binaryExpression = RuleExpressionToOneExpression<T>(ruleExpression, genericType);
                      var lambdaFunc = Expression.Lambda<Func<T, bool>>(binaryExpression, genericType);
                      return lambdaFunc.Compile();
                  }
              
                  private static Expression RuleExpressionToOneExpression<T>(RuleExpression ruleExpression, ParameterExpression genericType)
                  {
                      if (ruleExpression == null)
                      {
                          throw new ArgumentNullException();
                      }
                      Expression finalExpression;
                      //check if node is leaf
                      if (ruleExpression.NodeOperator == NodeOperator.Leaf)
                      {
                          return RuleToExpression<T>(ruleExpression.Rule, genericType);
                      }
                      //check if node is NodeOperator.And
                      if (ruleExpression.NodeOperator.Equals(NodeOperator.And))
                      {
                          finalExpression = Expression.Constant(true);
                          ruleExpression.Expressions.ForEach(expression =>
                          {
                              finalExpression = Expression.AndAlso(finalExpression, expression.NodeOperator.Equals(NodeOperator.Leaf) ? 
                                  RuleToExpression<T>(expression.Rule, genericType) :
                                  RuleExpressionToOneExpression<T>(expression, genericType));
                          });
                          return finalExpression;
                      }
                      //check if node is NodeOperator.Or
                      else
                      {
                          finalExpression = Expression.Constant(false);
                          ruleExpression.Expressions.ForEach(expression =>
                          {
                              finalExpression = Expression.Or(finalExpression, expression.NodeOperator.Equals(NodeOperator.Leaf) ?
                                  RuleToExpression<T>(expression.Rule, genericType) :
                                  RuleExpressionToOneExpression<T>(expression, genericType));
                          });
                          return finalExpression;
              
                      }      
                  }      
              
                  public static BinaryExpression RuleToExpression<T>(Rule rule, ParameterExpression genericType)
                  {
                      try
                      {
                          Expression value = null;
                          //Get Comparison property
                          var key = Expression.Property(genericType, rule.ComparisonPredicate);
                          Type propertyType = typeof(T).GetProperty(rule.ComparisonPredicate).PropertyType;
                          //convert case is it DateTimeOffset property
                          if (propertyType == typeof(DateTimeOffset))
                          {
                              var converter = TypeDescriptor.GetConverter(propertyType);
                              value = Expression.Constant((DateTimeOffset)converter.ConvertFromString(rule.ComparisonValue));
                          }
                          else
                          {
                              value = Expression.Constant(Convert.ChangeType(rule.ComparisonValue, propertyType));
                          }
                          BinaryExpression binaryExpression = Expression.MakeBinary(rule.ComparisonOperator, key, value);
                          return binaryExpression;
                      }
                      catch (FormatException)
                      {
                          throw new Exception("Exception in RuleToExpression trying to convert rule Comparison Value");
                      }
                      catch (Exception e)
                      {
                          throw new Exception(e.Message);
                      }
              
                  }
              

              【讨论】:

                【解决方案11】:

                我为用 dotnet 编写的丰富且高性能的规则引擎创建了一个 package,请查看 this repo 了解更多信息。 安装后,您可以像这样简单地使用它:

                var engine = new RulesService<TestModel>(new RulesCompiler(), new LazyCache.Mocks.MockCachingService());
                            
                    var matchingRules = engine.GetMatchingRules(
                        new TestModel { NumericField = 5 },
                        new[] {
                            new RulesConfig {
                                Id = Guid.NewGuid(),
                                RulesOperator = Rule.InterRuleOperatorType.And,
                                RulesGroups = new RulesGroup[] {
                                    new RulesGroup {
                                        RulesOperator = Rule.InterRuleOperatorType.And,
                                        Rules = new[] {
                                            new Rule { 
                                            ComparisonOperator = Rule.ComparisonOperatorType.Equal,
                                            ComparisonValue = 5.ToString(),
                                            ComparisonPredicate = nameof(TestModel.NumericField) 
                                            }
                                        }
                                    }
                                }
                            }
                        });
                

                【讨论】:

                  【解决方案12】:

                  我想添加微软的Rules Engine 库以供以后访问;

                  https://github.com/microsoft/RulesEngine

                  【讨论】:

                    猜你喜欢
                    • 2010-10-02
                    • 1970-01-01
                    • 1970-01-01
                    • 2010-09-10
                    • 2011-07-13
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多