我有一个db表,存储如下:

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")

由于对象的属性,如'年龄'或'user_name'存储在表中,以及比较操作符'great_than'和'equal',我怎么可能做到这一点?

c#是一种静态类型语言,所以不知道该如何发展。


当前回答

马丁的回答很好。实际上,我做了一个规则引擎,与他的想法相同。我很惊讶,这几乎是一样的。我加入了他的一些代码,以在一定程度上改进它。尽管我已经让它处理更复杂的规则。

你可以看看Yare。网

或者在Nuget下载

其他回答

尽管回答“如何实现规则引擎?”(在c#中)的问题是按顺序执行给定的一组规则,这通常被认为是naïve实现(并不意味着它不起作用:-)

在你的情况下,它似乎“足够好”,因为你的问题似乎更多的是“如何按顺序运行一组规则”,如果你有最新的c#版本,lambda/expression树(Martin的答案)肯定是最优雅的方式。

然而,对于更高级的场景,这里有一个到Rete算法的链接,它实际上在许多商业规则引擎系统中实现,还有一个到NRuler的链接,它是该算法在c#中的实现。

我想添加微软的规则引擎库,以供将来访问;

https://github.com/microsoft/RulesEngine

马丁的回答很好。实际上,我做了一个规则引擎,与他的想法相同。我很惊讶,这几乎是一样的。我加入了他的一些代码,以在一定程度上改进它。尽管我已经让它处理更复杂的规则。

你可以看看Yare。网

或者在Nuget下载

这段代码将规则编译成快速可执行的代码(使用表达式树),不需要任何复杂的switch语句:

(编辑:具有通用方法的完整工作示例)

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类型。您可以用泛型类型T替换User,从而为任何类型的对象提供泛型规则编译器。此外,代码应该处理错误,比如未知的操作符名称。

请注意,即使在引入表达式树API之前,使用Reflection.Emit也可以动态生成代码。LambdaExpression.Compile()方法使用Reflection。在被子下发射(您可以使用ILSpy看到这一点)。

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

您可以在没有工作流的情况下执行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