it-swarm.asia

كيفية تنفيذ محرك القاعدة؟

لدي جدول ديسيبل يخزن ما يلي:

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

نظرًا لأن خاصية الكائن مثل "age" أو "user_name" يتم تخزينها في الجدول ، بالإضافة إلى عامل المقارنة "great_than" و "يساوي" ، كيف يمكنني القيام بذلك؟

C # هي لغة مكتوبة بشكل ثابت ، لذلك لست متأكدًا من كيفية المضي قدمًا.

190
Blankman

يقوم هذا المقتطف بترجمة القواعد إلى تعليمات برمجية قابلة للتنفيذ بسرعة (باستخدام أشجار Expression ) ولا يحتاج إلى أي عبارات تبديل معقدة:

(تحرير: مثال عمل كامل مع طريقة عامة )

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:

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 للمشغل ، وبالتالي لا نحتاج إلى أي تعيين إضافي.

إذا كنت بحاجة إلى أسماء مخصصة ، فيمكنك إنشاء قاموس بسيط جدًا وترجمة جميع العوامل قبل تجميع القواعد:

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

يستخدم الكود نوع المستخدم للبساطة. يمكنك استبدال المستخدم بنوع T عام ليكون له مترجم قاعدة عام لأي أنواع الكائنات. أيضًا ، يجب أن يتعامل الرمز مع الأخطاء ، مثل اسم عامل غير معروف.

لاحظ أنه كان من الممكن إنشاء تعليمة برمجية سريعة حتى قبل تقديم واجهة تعبير تعبير الأشجار API ، باستخدام Reflection.Emit. تستخدم طريقة LambdaExpression.Compile () Reflection.Emit تحت الأغطية (يمكنك رؤية ذلك باستخدام ILSpy ).

370
Martin Konicek

فيما يلي بعض التعليمات البرمجية التي يتم تجميعها كما تفعل المهمة. استخدم أساسًا قواميس ، إحداهما تحتوي على مناظرة من أسماء المشغلين إلى الدوال المنطقية ، والأخرى تحتوي على خريطة من أسماء الخصائص الخاصة بنوع المستخدم إلى PropertyInfos المستخدمة لاستدعاء جامع الخصائص (إذا كانت عامة). يمكنك تمرير مثيل المستخدم ، والقيم الثلاث من الجدول الخاص بك إلى طريقة تطبيق ثابت.

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

لقد صممت محركًا للقواعد يستخدم مقاربة مختلفة عن تلك الموضحة في سؤالك ، لكنني أعتقد أنك ستجده أكثر مرونة من مقالك الحالي.

يبدو أن مقاربتك الحالية تركز على كيان واحد ، "المستخدم" ، وتحدد قواعدك المستمرة "property" و "operator" و "value". النمط الخاص بي ، بدلاً من ذلك يخزن رمز C # لأحد المسندات (Func <T ، bool>) في عمود "تعبير" في قاعدة البيانات الخاصة بي. في التصميم الحالي ، باستخدام توليد الشفرة ، أستعلم "القواعد" من قاعدة البيانات الخاصة بي وأجمع تجميعًا بأنواع "القاعدة" ، ولكل منها طريقة "اختبار". هنا هو التوقيع للواجهة التي يتم تنفيذها كل قاعدة:

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

يتم تصنيف "التعبير" كنص لطريقة "الاختبار" عند تنفيذ التطبيق لأول مرة. كما ترى ، فإن الأعمدة الأخرى في الجدول تظهر أيضًا كخصائص من الدرجة الأولى في القاعدة بحيث يتمتع المطور بالمرونة لإنشاء تجربة لكيفية إخطار المستخدم بالفشل أو النجاح.

يعد إنشاء تجميع في الذاكرة حدثًا يحدث مرة واحدة أثناء التطبيق الخاص بك وتحصل على مكسب في الأداء من خلال عدم الحاجة إلى استخدام التفكير عند تقييم القواعد الخاصة بك. يتم التحقق من التعبيرات الخاصة بك في وقت التشغيل حيث لن يتم إنشاء التجميع بشكل صحيح إذا كان هناك خطأ إملائي في اسم الخاصية ، إلخ.

آليات إنشاء تجميع في الذاكرة كالتالي:

  • تحميل القواعد الخاصة بك من DB
  • التكرار عبر القواعد ولكل منها ، باستخدام StringBuilder وبعض سلسلة السلسلة اكتب النص الذي يمثل فئة ترث من IDataRule
  • ترجمة باستخدام CodeDOM - مزيد من المعلومات

هذا في الواقع بسيط للغاية لأنه بالنسبة للغالبية فإن هذا الكود عبارة عن تطبيقات خاصية وتهيئة قيمة في المنشئ. إلى جانب ذلك ، الكود الآخر الوحيد هو التعبير.
ملاحظة: هناك قيود على أن تعبيرك يجب أن يكون .NET 2.0 (لا يوجد لامدا أو ميزات C # 3.0 أخرى) بسبب وجود قيود في CodeDOM.

هنا بعض التعليمات البرمجية عينة لذلك.

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

MOD CODE: كان هناك طلب للحصول على الكود المتعلق بإنشاء الكود. قمت بتغليف الوظيفة في فئة تسمى "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;
                }
            }
        }
    }

إذا كان هناك أي أخرى أسئلة أو تعليقات أو طلبات للحصول على مزيد من نماذج التعليمات البرمجية ، فأعلمني.

9
Glenn Ferrie

التفكير هو إجابتك الأكثر تنوعا. لديك ثلاثة أعمدة من البيانات ، ويجب معالجتها بطرق مختلفة:

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

كل هذا يتوقف على احتمالات المستقبل ....

8
Schroedingers Cat

ماذا عن نهج موجه لنوع البيانات مع طريقة التمديد:

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)));
6
Yann Olaf

إذا كان لديك عدد قليل فقط من الخصائص والمشغلين ، فإن المسار الأقل مقاومة هو فقط ترميز كل الفحوص كحالات خاصة مثل هذه:

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, object>.

إذا كنت لا تعرف أسماء الخصائص في وقت الترجمة ، أو كنت ترغب في تجنب حالات خاصة لكل خاصية ولا تريد استخدام نهج الجدول ، يمكنك استخدام الانعكاس للحصول على الخصائص. فمثلا:

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

ولكن نظرًا لأن TargetValue ربما string ، فستحتاج إلى الحرص على القيام بتحويل الكتابة من جدول القواعد إذا لزم الأمر.

6
Rick Sladkey

على الرغم من أن أكثر الطرق وضوحًا للإجابة على السؤال "كيفية تنفيذ مشغل قاعدة؟ (في C #)" هي تنفيذ مجموعة معينة من القواعد بالتسلسل ، إلا أنه يعتبر بشكل عام تطبيقًا ساذجًا (لا يعني أنه لا يعمل :-)

يبدو أنه "جيد بما فيه الكفاية" في قضيتك لأن مشكلتك تبدو أكثر "كيفية تشغيل مجموعة من القواعد بالتسلسل" ، وشجرة lambda/تعبير (إجابة مارتن) هي بالتأكيد الطريقة الأكثر أناقة في هذا الشأن إذا كنت مجهزة بالإصدارات الأخيرة من C #.

ومع ذلك ، فيما يتعلق بالسيناريوهات الأكثر تقدمًا ، يوجد هنا ارتباط بـ Rete Algorithm التي يتم تنفيذها في الواقع في العديد من أنظمة محركات القواعد التجارية ، ورابط آخر إلى NRuler ، وهو تطبيق لتلك الخوارزمية في C #.

4
Simon Mourier

كانت إجابة مارتن جيدة. لقد صنعت في الواقع محرك قواعد لديه نفس الفكرة التي لديه. وفوجئت أنه هو نفسه تقريبا. لقد شملت بعض التعليمات البرمجية لتحسينه بعض الشيء. على الرغم من أنني جعلت من التعامل مع قواعد أكثر تعقيدا.

يمكنك إلقاء نظرة على Yare.NET

أو قم بتنزيله في Nuget

3
aiapatag

ماذا عن استخدام محرك قواعد سير العمل؟

يمكنك تنفيذ قواعد سير عمل Windows بدون سير العمل ، انظر مدونة Guy Burstein: http://blogs.Microsoft.co.il/blogs/bursteg/archive/2006/10/11/RuleExecutionWithoutWorkflow.aspx

ولإنشاء القواعد الخاصة بك بشكل برمجي ، راجع WebLog الخاص بـ Stephen Kaufman

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

2
Kevin Burton

لقد قمت بإضافة تطبيق لـ ، أو بين القواعد التي أضفتها إلى فئة

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
}

لدي فئة أخرى تقوم بترجمة القاعدة Expression إلى 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);
        }

    }
1
Max.Futerman

أواجه مشكلة حساسة لحالة الأحرف في إجابة Martin Konicek ، لذلك إذا كنت ترغب في ألا يكون رمز rule.MemberName حساسًا لحالة الأحرف ، فقم فقط بإضافة

var tProp = typeof(User).GetProperty(r.MemberName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).PropertyType;
0
Max.Futerman