我有一个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#是一种静态类型语言,所以不知道该如何发展。


当前回答

下面是一些按原样编译并完成这项工作的代码。 基本上使用两个字典,一个包含从操作符名称到布尔函数的映射,另一个包含从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"));
    }
}

其他回答

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

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

下面是一些按原样编译并完成这项工作的代码。 基本上使用两个字典,一个包含从操作符名称到布尔函数的映射,另一个包含从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"));
    }
}

如果你只有少量的属性和操作符,那么阻力最小的方法就是将所有检查都编码为如下的特殊情况:

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<User,对象>。

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

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

但是由于TargetValue可能是一个字符串,如果有必要,您需要注意从规则表进行类型转换。

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

你可以看看Yare。网

或者在Nuget下载

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

https://github.com/microsoft/RulesEngine