Làm thế nào để thực hiện một công cụ quy tắc?


205

Tôi có một bảng db lưu trữ như sau:

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

Bây giờ nói rằng tôi có một bộ sưu tập các quy tắc sau:

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

Bây giờ tôi cũng có một ví dụ của một người dùng:

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

Làm thế nào tôi có thể lặp qua các quy tắc này, và áp dụng logic và thực hiện các so sánh, v.v.

if(user.age > 15)

if(user.username == "some_name")

Vì thuộc tính của đối tượng như 'tuổi' hoặc 'user_name' được lưu trữ trong bảng, cùng với toán tử so sánh 'great_than' và 'bằng', làm thế nào tôi có thể làm điều này?

C # là một ngôn ngữ gõ tĩnh, vì vậy không biết làm thế nào để đi tiếp.

Câu trả lời:


390

Đoạn mã này biên dịch Quy tắc thành mã thực thi nhanh (sử dụng cây Biểu thức ) và không cần bất kỳ câu lệnh chuyển đổi phức tạp nào:

(Chỉnh sửa: ví dụ làm việc đầy đủ với phương pháp chung )

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();
}

Sau đó bạn có thể viết:

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));
}

Đây là cách triển khai 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);
    }
}

Lưu ý rằng tôi đã sử dụng 'GreaterThan' thay vì 'Greater_than', v.v. - điều này là do 'GreaterThan' là tên .NET cho toán tử, do đó chúng tôi không cần thêm ánh xạ.

Nếu bạn cần tên tùy chỉnh, bạn có thể xây dựng một từ điển rất đơn giản và chỉ cần dịch tất cả các toán tử trước khi biên dịch các quy tắc:

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

Mã sử ​​dụng loại Người dùng để đơn giản. Bạn có thể thay thế Người dùng bằng loại T chung để có trình biên dịch Quy tắc chung cho mọi loại đối tượng. Ngoài ra, mã nên xử lý lỗi, như tên nhà khai thác không xác định.

Lưu ý rằng việc tạo mã nhanh chóng là có thể ngay cả trước khi API cây biểu hiện được giới thiệu, sử dụng Reflection.Emit. Phương thức LambdaExpression.Compile () sử dụng Reflection.Emit dưới vỏ bọc (bạn có thể thấy điều này bằng ILSpy ).


Tôi có thể đọc thêm về câu trả lời của bạn ở đâu để tìm hiểu các lớp / đối tượng / v.v. bạn có trong mã của bạn? Nó chủ yếu là cây biểu hiện?
Blankman

4
Tất cả các lớp đến từ không gian tên System.Linq.Expressions và tất cả được tạo bằng các phương thức xuất xưởng của lớp Expression - gõ "Expression". trong IDE của bạn để truy cập tất cả chúng. Đọc thêm về cây Biểu hiện ở đây msdn.microsoft.com/en-us/l
Library / bb39951.aspx

3
@Martin tôi có thể tìm danh sách tên nhà khai thác .NET đủ điều kiện ở đâu?
Brian Graham

5
@Dark Trượt dòng Bạn có thể tìm thấy chúng ở đây msdn.microsoft.com/en-us/l Library / bb361179.aspx. Không phải tất cả chúng đều là biểu thức boolean - chỉ sử dụng các biểu thức boolean (như GreaterThan, NotEqual, v.v.).
Martin Konicek

1
@BillDaugherty Quy tắc một lớp giá trị đơn giản với ba thuộc tính: MemberName, Toán tử, TargetValue. Ví dụ: Quy tắc mới ("Tuổi", "GreaterThan", "20").
Martin Konicek

14

Đây là một số mã biên dịch như là và thực hiện công việc. Về cơ bản sử dụng hai từ điển, một từ có chứa ánh xạ từ tên toán tử đến các hàm boolean và một từ điển chứa bản đồ từ tên thuộc tính của loại Người dùng đến PropertyInfos được sử dụng để gọi trình getter thuộc tính (nếu công khai). Bạn chuyển thể hiện của Người dùng và ba giá trị từ bảng của bạn sang phương thức Áp dụng tĩnh.

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

9

Tôi đã xây dựng một công cụ quy tắc có cách tiếp cận khác với cách bạn nêu trong câu hỏi của bạn, nhưng tôi nghĩ bạn sẽ thấy nó linh hoạt hơn nhiều so với cách tiếp cận hiện tại của bạn.

Cách tiếp cận hiện tại của bạn dường như tập trung vào một thực thể duy nhất, "Người dùng" và các quy tắc liên tục của bạn xác định "propertyname", "toán tử" và "value". Mẫu của tôi, thay vào đó lưu trữ mã C # cho một vị từ (Func <T, bool>) trong cột "Biểu thức" trong cơ sở dữ liệu của tôi. Trong thiết kế hiện tại, sử dụng việc tạo mã, tôi đang truy vấn "quy tắc" từ cơ sở dữ liệu của mình và biên dịch một tập hợp với các loại "Quy tắc", mỗi loại có một phương pháp "Thử nghiệm". Đây là chữ ký cho giao diện được triển khai mỗi Quy tắ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; }
}

"Biểu thức" được biên dịch thành phần thân của phương thức "Thử nghiệm" khi ứng dụng lần đầu tiên thực thi. Như bạn có thể thấy các cột khác trong bảng cũng được hiển thị dưới dạng các thuộc tính hạng nhất theo quy tắc để nhà phát triển có thể linh hoạt tạo ra trải nghiệm về cách người dùng được thông báo về thất bại hoặc thành công.

Tạo một cụm trong bộ nhớ là một lần xuất hiện trong ứng dụng của bạn và bạn có được hiệu suất đạt được bằng cách không phải sử dụng sự phản chiếu khi đánh giá các quy tắc của bạn. Biểu thức của bạn được kiểm tra trong thời gian chạy vì tập hợp sẽ không tạo chính xác nếu tên thuộc tính bị sai chính tả, v.v.

Các cơ chế tạo một bộ nhớ trong như sau:

  • Tải quy tắc của bạn từ DB
  • Lặp lại các quy tắc và cho mỗi quy tắc, sử dụng StringBuilder và một số nối chuỗi, viết Văn bản đại diện cho một lớp kế thừa từ IDataRule
  • biên dịch bằng CodeDOM - thêm thông tin

Điều này thực sự khá đơn giản bởi vì phần lớn mã này là các cài đặt thuộc tính và khởi tạo giá trị trong hàm tạo. Bên cạnh đó, mã duy nhất khác là Biểu thức.
LƯU Ý: có một hạn chế là biểu thức của bạn phải là .NET 2.0 (không có lambdas hoặc các tính năng C # 3.0 khác) do giới hạn trong CodeDOM.

Đây là một số mã mẫu cho điều đó.

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

Ngoài ra, tôi đã tạo một lớp mà tôi gọi là "DataRuleCollection", đã triển khai ICollection>. Điều này cho phép tôi tạo ra khả năng "TestAll" và một bộ chỉ mục để thực hiện quy tắc cụ thể theo tên. Dưới đây là các triển khai cho hai phương pháp đó.

    /// <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);
    }

MÃ SỐ HƠN: Có một yêu cầu về mã liên quan đến việc tạo mã. Tôi đã gói gọn chức năng trong một lớp có tên 'RulesAssuggingGenerator' mà tôi đã đưa vào dưới đây.

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;
                }
            }
        }
    }

Nếu có bất kỳ khác thắc mắc hoặc ý kiến hoặc yêu cầu về mẫu mã hơn nữa, cho tôi biết.


Bạn đúng rằng công cụ này có thể được tạo ra chung chung hơn và API CodeDOM chắc chắn cũng là một tùy chọn. Có lẽ thay vì mã "sb.AppendLine" không rõ ràng, bạn có thể chỉ ra chính xác bạn gọi CodeDOM như thế nào?
Martin Konicek

8

Phản ánh là câu trả lời linh hoạt nhất của bạn. Bạn có ba cột dữ liệu và chúng cần được xử lý theo các cách khác nhau:

  1. Tên lĩnh vực của bạn. Phản ánh là cách để có được giá trị từ một tên trường được mã hóa.

  2. Toán tử so sánh của bạn. Cần có một số lượng hạn chế trong số này, vì vậy một tuyên bố trường hợp nên xử lý chúng dễ dàng nhất. Đặc biệt là một số trong số họ (có một hoặc nhiều) phức tạp hơn một chút.

  3. Giá trị so sánh của bạn. Nếu đây là tất cả các giá trị thẳng thì điều này là dễ dàng, mặc dù bạn sẽ chia nhiều mục lên. Tuy nhiên, bạn cũng có thể sử dụng sự phản chiếu nếu chúng cũng là tên trường.

Tôi sẽ có một cách tiếp cận như:

    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)

Vân vân.

Nó cho phép bạn linh hoạt để thêm nhiều tùy chọn để so sánh. Điều đó cũng có nghĩa là bạn có thể viết mã trong các phương thức So sánh bất kỳ xác thực kiểu nào mà bạn có thể muốn và làm cho chúng phức tạp như bạn muốn. Ngoài ra còn có tùy chọn ở đây để So sánh được đánh giá là cuộc gọi đệ quy trở lại một dòng khác hoặc dưới dạng giá trị trường, có thể được thực hiện như sau:

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

Tất cả phụ thuộc vào khả năng cho tương lai ....


Và bạn có thể lưu trữ các cụm / đối tượng được phản ánh của bạn, điều này sẽ làm cho mã của bạn hiệu quả hơn nữa.
Mrchief

7

Nếu bạn chỉ có một số thuộc tính và toán tử, đường dẫn của mức kháng cự tối thiểu là chỉ mã hóa tất cả các kiểm tra như các trường hợp đặc biệt như sau:

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;
}

Nếu bạn có nhiều thuộc tính, bạn có thể tìm thấy một cách tiếp cận dựa vào bảng dễ đọc hơn. Trong trường hợp đó, bạn sẽ tạo một tĩnh Dictionaryánh xạ tên thuộc tính cho các đại biểu khớp, giả sử , Func<User, object>.

Nếu bạn không biết tên của các thuộc tính tại thời điểm biên dịch hoặc bạn muốn tránh các trường hợp đặc biệt cho từng thuộc tính và không muốn sử dụng cách tiếp cận bảng, bạn có thể sử dụng phản xạ để lấy các thuộc tính. Ví dụ:

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

Nhưng vì TargetValuecó lẽ là một string, bạn sẽ cần cẩn thận thực hiện chuyển đổi loại từ bảng quy tắc nếu cần.


giá trị nào.CompareTo (giới hạn) trả về? -1 0 hay 1? Chưa thấy b4!
Blankman

1
@Blankman: Đóng: nhỏ hơn 0, 0 hoặc lớn hơn 0. IComparableđược sử dụng để so sánh mọi thứ. Dưới đây là các tài liệu: IComparable.CompareTo Phương thức .
Rick Sladkey

2
Tôi không hiểu tại sao câu trả lời này đã được bình chọn. Nó vi phạm nhiều nguyên tắc thiết kế: "Nói không hỏi" => mỗi quy tắc nên được yêu cầu trả lại kết quả. "Mở để mở rộng / đóng để sửa đổi" => bất kỳ quy tắc mới nào có nghĩa là phương thức ApplyRules cần sửa đổi. Cộng với mã là khó hiểu trong nháy mắt.
Xuất hiện

2
Thật vậy, con đường ít kháng cự nhất hiếm khi là con đường tốt nhất. Xin vui lòng xem và upvote câu trả lời cây biểu hiện xuất sắc.
Rick Sladkey

6

Điều gì về một cách tiếp cận định hướng kiểu dữ liệu với một phương pháp mở rộng:

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

Hơn bạn có thể gợi lên như thế này:

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

4

Mặc dù cách rõ ràng nhất để trả lời câu hỏi "Cách triển khai công cụ quy tắc? (Trong C #)" là thực thi một bộ quy tắc nhất định theo trình tự, nhưng điều này nói chung được coi là một triển khai ngây thơ (không có nghĩa là nó không hoạt động :-)

Có vẻ như nó "đủ tốt" trong trường hợp của bạn bởi vì vấn đề của bạn dường như là "làm thế nào để chạy một bộ quy tắc theo trình tự" và cây lambda / biểu thức (câu trả lời của Martin) chắc chắn là cách thanh lịch nhất trong vấn đề đó nếu bạn được trang bị các phiên bản C # gần đây.

Tuy nhiên, đối với các kịch bản nâng cao hơn, đây là một liên kết đến Thuật toán Rete thực tế được triển khai trong nhiều hệ thống công cụ quy tắc thương mại và một liên kết khác đến NRuler , một triển khai thuật toán đó trong C #.


3

Câu trả lời của Martin khá hay. Tôi thực sự đã tạo ra một công cụ quy tắc có cùng ý tưởng với mình. Và tôi đã ngạc nhiên rằng nó gần như giống nhau. Tôi đã bao gồm một số mã của anh ấy để phần nào cải thiện nó. Mặc dù tôi đã thực hiện nó để xử lý các quy tắc phức tạp hơn.

Bạn có thể nhìn vào Yare.NET

Hoặc tải xuống trong Nuget


2

Làm thế nào về việc sử dụng các công cụ quy tắc công việc?

Bạn có thể thực thi Quy tắc luồng công việc của Windows mà không cần luồng công việc, xem Blog của Guy Burstein: http://blogs.microsoft.co.il/bloss/bursteg/archive/2006/10/11/RuleExecutWithoutWorkflow.aspx

và để lập trình các quy tắc của bạn, hãy xem WebLog của Stephen Kaufman

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


2

Tôi đã thêm triển khai cho và, hoặc giữa các quy tắc tôi đã thêm lớp RuleExpression đại diện cho gốc của cây có thể là quy tắc đơn giản hoặc có thể và, hoặc các biểu thức nhị phân ở đó vì chúng không có quy tắc và có biểu thức:

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
}

Tôi có một lớp khác biên dịch ruleExpression thành một Func<T, bool>:

 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);
        }

    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.