本文介绍了如何实现一个规则引擎?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a db table that stores the following:

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

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

Now say I have a collection of these rules:

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

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

Now I have an instance of a user also:

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

我怎么会遍历这些规则,并应用逻辑和执行比较等?

How would I loop through these rules, and apply the logic and perform the comparisons etc?

if(user.age > 15)

if(user.username == "some_name")

由于像'年龄'或'USER_NAME'对象的属性被存储在表中,用比较算'great_than和平等,我可以怎么能这样相处?

Since the object's property like 'age' or 'user_name' is stored in the table, along with the comparison operater 'great_than' and 'equal', how could I possible do this?

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

C# is a statically typed language, so not sure how to go forward.

推荐答案

本段编译规则分为快可执行code (使用的),并且不需要任何复杂的开关语句:

This snippet compiles the Rules into fast executable code (using Expression trees) and does not need any complicated switch statements:

(编辑:)

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", "20"),
    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执行:

Here is the implementation of 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 == 15'
        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的名字,因此我们并不需要任何额外的映射

Note that I used 'GreaterThan' instead of 'greater_than' etc. - this is because 'GreaterThan' is the .NET name for the operator, therefore we don't need any extra mapping.

如果你真的需要自定义名称,你可以建立一个非常简单的字典和刚编译规则之前把所有运营商:

If you really need custom names you can build a very simple dictionary and just translate all operators before compiling the rules:

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

注意code使用为简单类型用户。你可以用一个通用的类型T更换用户有一个任何类型的对象。

另外请注意:在飞行中发生code连前pression树木API被引入,使用Reflection.Emit的面前是可能的。该方法LambdaEx pression.Compile()使用Reflection.Emit的下盖(你可以看到这一点使用)

Also note: generating code on the fly was possible even before the Expression trees API was introduced, using Reflection.Emit. The method LambdaExpression.Compile() uses Reflection.Emit under the covers (you can see this using ILSpy).

这篇关于如何实现一个规则引擎?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-24 17:02