কিভাবে একটি রুল ইঞ্জিন বাস্তবায়ন?


205

আমার কাছে একটি ডিবি টেবিল রয়েছে যা নিম্নলিখিতগুলি সঞ্চয় করে:

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

যেহেতু 'বয়স' বা 'ব্যবহারকারীর নাম' এর মতো অবজেক্টের সম্পত্তি টেবিলে সংরক্ষণ করা হয়, তুলনা অপারেটর 'গ্রেট_থান' এবং 'সমান' এর সাথে আমি কীভাবে এটি সম্ভব?

সি # হ'ল স্থিতিযুক্ত টাইপিত ভাষা, সুতরাং কীভাবে এগিয়ে যাবেন তা নিশ্চিত নন।

উত্তর:


390

এই স্নিপেটটি দ্রুত সম্পাদনযোগ্য কোডে ( এক্সপ্রেশন ট্রি ব্যবহার করে ) বিধিগুলি সঙ্কলন করে এবং কোনও জটিল সুইচ স্টেটমেন্টের প্রয়োজন হয় না:

(সম্পাদনা করুন: জেনেরিক পদ্ধতি সহ পুরো কাজের উদাহরণ )

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

এখানে বিল্ডএক্সপিআর বাস্তবায়ন:

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

নোট করুন যে আমি 'গ্রেটার_থান' ইত্যাদির পরিবর্তে 'গ্রেটারথান' ব্যবহার করেছি - এটি কারণ 'গ্রেটারথান' অপারেটরের জন্য। নেট নাম, সুতরাং আমাদের কোনও অতিরিক্ত ম্যাপিংয়ের দরকার নেই।

আপনার যদি কাস্টম নামের প্রয়োজন হয় আপনি খুব সাধারণ অভিধান তৈরি করতে পারেন এবং নিয়মগুলি সংকলনের আগে সমস্ত অপারেটরকে কেবল অনুবাদ করতে পারেন:

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

কোড সরলতার জন্য ব্যবহারকারী টাইপ ব্যবহার করে। একটিতে জেনেরিক টাইপ টি দিয়ে আপনি ব্যবহারকারীকে প্রতিস্থাপন করতে পারেনজেনেরিক বিধি সংকলকটি কোনও ধরণের অবজেক্টের জন্য । এছাড়াও, কোডটিতে অজানা অপারেটরের নামের মতো ত্রুটিগুলি পরিচালনা করা উচিত।

দ্রষ্টব্য যে প্রতিচ্ছবি ট্রি এপিআই প্রবর্তনের আগেও ফ্লাইতে কোড উত্পন্ন কোড সম্ভব ছিল, প্রতিচ্ছবি ব্যবহার করে mit LambdaExpression.Compile পদ্ধতিটি () প্রতিচ্ছবি ব্যবহার করে covers কভারগুলির নীচে প্রেরণ করুন (আপনি এটি ILSpy ব্যবহার করে দেখতে পারেন )।


ক্লাস / অবজেক্টস / ইত্যাদি শিখতে আপনার উত্তর সম্পর্কে আমি আরও কোথায় পড়তে পারি? আপনার কোড আপনার আছে? এটি বেশিরভাগই অভিব্যক্তি গাছ?
ফাঁকা 0

4
সমস্ত ক্লাস নেমস্পেস সিস্টেম থেকে এসেছে in লিংক.এক্সপ্রেসানস, এবং সমস্ত এক্সপ্রেশন ক্লাসের কারখানা পদ্ধতি ব্যবহার করে তৈরি করা হয়েছে - টাইপ "এক্সপ্রেশন"। আপনার আইডিইতে সেগুলি অ্যাক্সেস করতে। এক্সপ্রেশন গাছ সম্পর্কে এখানে আরও পড়ুন msdn.microsoft.com/en-us/library/bb397951.aspx
মার্টিন Konicek

3
@ মার্টিন আমি কোথাও যোগ্য। নেট অপারেটর নামের একটি তালিকা পেতে পারি?
ব্রায়ান গ্রাহাম

5
@ ডার্ক স্লিপস্ট্রিম আপনি তাদের এখানে এমএসডিএন.ইমিক্রোসফটি.ওইন / লিবেরি / বিবি 361179.aspx পেতে পারেন। এগুলির সবগুলিই বুলিয়ান এক্সপ্রেশন নয় - কেবলমাত্র বুলিয়ান ব্যবহার করুন (যেমন গ্রেটারথান, নোটইকুয়াল ইত্যাদি)।
মার্টিন কোনিসেক

1
@ বিলডৌথের্টি একটি সাধারণ মান শ্রেণীর বিধি রাখুন যার মধ্যে তিনটি বৈশিষ্ট্য রয়েছে: সদস্যের নাম, অপারেটর, টার্গেটভ্যালু। উদাহরণস্বরূপ, নতুন বিধি ("বয়স", "গ্রেটারথান", "20")।
মার্টিন কোনিসেক

14

এখানে কিছু কোড রয়েছে যা কাজ হিসাবে সংকলিত হয়। মূলত দুটি অভিধান ব্যবহার করুন, একটি বুলিয়ান ফাংশনগুলির জন্য অপারেটরের নামগুলি থেকে ম্যাপিং এবং অন্যটি সম্পত্তি ব্যবহারকারীর (যদি জনসাধারণের জন্য) অনুরোধ করার জন্য ব্যবহার করা হয় প্রপার্টিআইএনফোস-এ ব্যবহারকারীর নামের নাম থেকে ম্যাপযুক্ত another আপনি ব্যবহারকারীর উদাহরণটি এবং তিনটি মানকে আপনার টেবিল থেকে স্থির প্রয়োগ পদ্ধতিতে পাস করুন।

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

আমি একটি নিয়ম ইঞ্জিন তৈরি করেছি যা আপনাকে আপনার প্রশ্নের বর্ণনার চেয়ে আলাদা পদ্ধতির গ্রহণ করে তবে আমি মনে করি আপনি এটি আপনার বর্তমান পদ্ধতির চেয়ে অনেক বেশি নমনীয় বলে মনে করবেন।

আপনার বর্তমান পদ্ধতির একক সত্তা, "ব্যবহারকারী" এবং আপনার অবিচলিত নিয়মগুলি "সম্পত্তি নাম", "অপারেটর" এবং "মান" সনাক্ত করে seems আমার প্যাটার্নটি পরিবর্তে আমার ডেটাবেজে "এক্সপ্রেশন" কলামে প্রিডিট (ফানক <টি, বুল>) এর জন্য সি # কোড সঞ্চয় করে। বর্তমান নকশায়, কোড জেনারেশনটি ব্যবহার করে আমি আমার ডাটাবেস থেকে "বিধিগুলি" জিজ্ঞাসা করছি এবং "বিধি" প্রকারের সাথে একটি অ্যাসেমবিলি সংকলন করছি, প্রতিটি "পরীক্ষার" পদ্ধতি সহ with প্রতিটি নিয়ম প্রয়োগ করা হয় ইন্টারফেসের জন্য স্বাক্ষর এখানে:

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

"এক্সপ্রেশন" অ্যাপ্লিকেশন প্রথম চালিত হলে "টেস্ট" পদ্ধতির মূল অংশ হিসাবে সংকলিত হয়। আপনি দেখতে পাচ্ছেন যে টেবিলের অন্যান্য কলামগুলিও নিয়মে প্রথম শ্রেণির বৈশিষ্ট্য হিসাবে প্রকাশিত হয়েছে যাতে কোনও বিকাশকারী বা সাফল্যের বিষয়ে ব্যবহারকারীকে কীভাবে বিজ্ঞপ্তি দেওয়া হয় তার জন্য কোনও বিকাশকারী একটি অভিজ্ঞতা তৈরি করতে নমনীয়তা অর্জন করতে পারেন।

আপনার অ্যাপ্লিকেশন চলাকালীন একটি ইন-মেমরি অ্যাসেমব্লিলিটি তৈরি করা 1-বারের ঘটনা এবং আপনার বিধিগুলি মূল্যায়নের সময় প্রতিচ্ছবি ব্যবহার না করে আপনি একটি পারফরম্যান্স লাভ পান। আপনার অভিব্যক্তি রানটাইমের সময় যাচাই করা হয়েছে কারণ কোনও সম্পত্তির নামের বানান ইত্যাদি থাকলে সমাবেশটি সঠিকভাবে উত্পন্ন হয় না etc.

ইন-মেমরি এসেম্বলি তৈরির মেকানিকগুলি নিম্নরূপ:

  • আপনার নিয়মগুলি ডিবি থেকে লোড করুন
  • স্ট্রিংবিল্ডার এবং কিছু স্ট্রিং কনটেনটেশন ব্যবহার করে নিয়মগুলি এবং প্রত্যেকটির জন্য পুনরাবৃত্তি করুন IDataRule থেকে উত্তরাধিকার সূত্রে প্রাপ্ত শ্রেণীর প্রতিনিধিত্ব করে পাঠ্য লিখুন
  • কোডডোম ব্যবহার করে সংকলন করুন - আরও তথ্য

এটি আসলে বেশ সহজ কারণ সংখ্যাগরিষ্ঠর জন্য এই কোডটি নির্মাণকারীতে সম্পত্তি বাস্তবায়ন এবং মান সূচনা। তা ছাড়া, কেবলমাত্র অন্য কোডটি হ'ল এক্সপ্রেশন।
দ্রষ্টব্য: কোডডোমে একটি সীমাবদ্ধতার কারণে আপনার অভিব্যক্তিটি .NET 2.0 (কোনও ল্যাম্বডাস বা অন্যান্য সি # 3.0 বৈশিষ্ট্য নয়) হওয়া উচিত।

এখানে তার জন্য কিছু নমুনা কোড দেওয়া আছে।

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

এর বাইরে আমি একটি ক্লাস করেছি যা আমি "ডেটাআরোলকলেশন" নামে পরিচিত, যা আইকোলিকেশন> প্রয়োগ করে। এটি আমাকে "টেস্টএল" সক্ষমতা তৈরি করতে সক্ষম করেছে এবং নাম অনুসারে একটি নির্দিষ্ট নিয়ম চালানোর জন্য একটি সূচক তৈরি করতে সক্ষম করেছে। এই দুটি পদ্ধতির বাস্তবায়ন এখানে দেওয়া হল।

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

আরও কোড: কোড জেনারেশন সম্পর্কিত কোডের জন্য একটি অনুরোধ ছিল। আমি নীচে অন্তর্ভুক্ত করেছি এমন 'RuleAsorsesGenerator' নামক শ্রেণিতে কার্যকারিতাটি encapsulated করেছি।

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

যদি থেকে থাকে তাহলে অন্যান্য প্রশ্ন বা মন্তব্য বা আরও কোড নমুনার জন্য অনুরোধ, আমাকে জানাতে।


আপনি ঠিক বলেছেন যে ইঞ্জিনটিকে আরও জেনেরিক করা যায় এবং কোডডোম এপিআই অবশ্যই একটি বিকল্প। সম্ভবত "sb.AppendLine" কোডটি পরিবর্তে যা খুব পরিষ্কার নয়, আপনি কোডডমকে ঠিক কীভাবে আবেদন করেছিলেন তা আপনি প্রদর্শন করতে পারেন?
মার্টিন কোনিসেক

8

প্রতিবিম্ব আপনার সবচেয়ে বহুমুখী উত্তর। আপনার কাছে ডেটার তিনটি কলাম রয়েছে এবং সেগুলি বিভিন্ন উপায়ে চিকিত্সা করা দরকার:

  1. আপনার ক্ষেত্রের নাম প্রতিচ্ছবি হ'ল কোডেড ক্ষেত্রের নাম থেকে মান পাওয়ার উপায়।

  2. আপনার তুলনা অপারেটর। এগুলির একটি সীমাবদ্ধ সংখ্যা থাকতে হবে, তাই কেস স্টেটমেন্টে এগুলি খুব সহজে পরিচালনা করা উচিত। বিশেষত তাদের মধ্যে কিছু (যার এক বা একাধিক রয়েছে) কিছুটা জটিল more

  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)

ইত্যাদি ইত্যাদি

এটি আপনাকে তুলনার জন্য আরও বিকল্প যুক্ত করার জন্য নমনীয়তা দেয়। এর অর্থ হ'ল আপনি তুলনা পদ্ধতিগুলির মধ্যে যে কোনও প্রকারের যাচাই করতে পারেন যা যা যাচাই করতে পারেন এবং আপনি যেমন চান তেমন জটিল করে তুলতে পারেন। কম্পেরিটোর জন্য এখানে অন্য বিকল্প রয়েছে যা অন্য লাইনে ফিরে আসা পুনরাবৃত্ত কল হিসাবে বা ক্ষেত্রের মান হিসাবে মূল্যায়ন করা যায় যা এই জাতীয় কাজগুলি করা যেতে পারে:

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

এটি সব ভবিষ্যতের সম্ভাবনার উপর নির্ভর করে ....


এবং আপনি আপনার প্রতিবিম্বিত সমাবেশ / অবজেক্টগুলি ক্যাশে করতে পারেন যা আপনার কোডটিকে আরও পারফরম্যান্ট করে তুলবে।
মিচিফ

7

আপনার কাছে যদি হাতে গোনা কয়েকটি সম্পত্তি এবং অপারেটর থাকে তবে সর্বনিম্ন প্রতিরোধের পথটি সমস্ত চেককে এই জাতীয় বিশেষ ক্ষেত্র হিসাবে কোড আপ করতে হবে:

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, তাই আপনাকে প্রয়োজনে নিয়ম ছক থেকে টাইপ রূপান্তর করার জন্য যত্ন নেওয়া দরকার।


মানটি কী করে? কমপিটারটিও (সীমা) ফিরে আসে? -1 0 বা 1? দেখেনি যে বি 4!
খালি

1
@ ব্লাঙ্কম্যান: বন্ধ: শূন্যের চেয়ে কম, শূন্য বা শূন্যের চেয়ে বড়। IComparableজিনিস তুলনা করার জন্য ব্যবহৃত হয়। এখানে দস্তাবেজগুলি রয়েছে: আইকোম্পারেবল .কম্পেরটো পদ্ধতি
রিক স্ল্যাডকি

2
কেন এই উত্তরটি আপ-ভোট দেওয়া হয়েছে তা আমি বুঝতে পারি না। এটি অনেকগুলি ডিজাইনের নীতিগুলি লঙ্ঘন করে: "জিজ্ঞাসা করবেন না বলুন" => নিয়মগুলির প্রত্যেককেই ফলাফল ফিরিয়ে দিতে বলা উচিত । "পরিবর্তনের জন্য খোলা / সংশোধনের জন্য বন্ধ" => যে কোনও নতুন নিয়মের মানে প্রয়োগের পদ্ধতিতে পরিবর্তনের প্রয়োজন। এছাড়াও কোডটি এক নজরে বোঝা মুশকিল।
অ্যাপটিটার

2
প্রকৃতপক্ষে, সর্বনিম্ন প্রতিরোধের পথটি খুব কমই সেরা পথ। দয়া করে চমৎকার অভিব্যক্তি গাছের উত্তরটি দেখুন এবং তার উপরে নজর দিন।
রিক স্ল্যাডকি

6

কোনও এক্সটেনশন পদ্ধতিতে ডেটা টাইপ ওরিয়েন্টেটেড পদ্ধতির বিষয়ে কী:

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

4

যদিও "কোনও নিয়ম ইঞ্জিন কীভাবে প্রয়োগ করবেন? (সি # তে") প্রশ্নের উত্তর দেওয়ার সর্বাধিক সুস্পষ্ট উপায় হ'ল ক্রমানুসারে প্রদত্ত নিয়মের একটি সেটটি কার্যকর করা, এটি সাধারণভাবে একটি নির্বিকার বাস্তবায়ন হিসাবে বিবেচিত হয় (এর অর্থ এই নয় যে এটি কার্যকর হয় না) :-)

আপনার ক্ষেত্রে এটি "যথেষ্ট ভাল" বলে মনে হচ্ছে কারণ আপনার সমস্যাটি "ক্রম অনুসারে নিয়মের একটি সেট কীভাবে চালানো যায়" বলে মনে হয় এবং ল্যাম্বডা / এক্সপ্রেশন ট্রি (মার্টিনের উত্তর) অবশ্যই এই ক্ষেত্রে সবচেয়ে মার্জিত উপায় যদি আপনি সাম্প্রতিক সি # সংস্করণে সজ্জিত করা হয়েছে।

তবে আরও উন্নত পরিস্থিতিগুলির জন্য, এখানে রিট অ্যালগরিদমের একটি লিঙ্ক রয়েছে যা বাস্তবে অনেক বাণিজ্যিক নিয়ম ইঞ্জিন সিস্টেমে প্রয়োগ করা হয় এবং এনআরুলারের সাথে অন্য একটি লিঙ্ক , সি # তে সেই অ্যালগরিদমের একটি বাস্তবায়ন।


3

মার্টিনের উত্তর বেশ ভাল ছিল। আমি আসলে একটি নিয়ম ইঞ্জিন তৈরি করেছিলাম যা তার মতো ধারণা। এবং আমি অবাক হয়েছিল যে এটি প্রায় একই রকম। কিছুটা উন্নতি করার জন্য আমি তার কয়েকটি কোড অন্তর্ভুক্ত করেছি। যদিও আমি আরও জটিল নিয়ম পরিচালনা করতে এটি তৈরি করেছি।

আপনি Yare.NET তাকান পারেন

অথবা এটি নুজেটে ডাউনলোড করুন


2

ওয়ার্কফ্লো বিধি ইঞ্জিন ব্যবহার সম্পর্কে কীভাবে?

ওয়ার্কফ্লো ছাড়াই আপনি উইন্ডোজ ওয়ার্কফ্লো বিধিগুলি কার্যকর করতে পারেন গাই বার্স্টেইনের ব্লগ দেখুন: http://blogs.microsoft.co.il/blogs/bursteg/archive/2006/10/11/ RuleExecutionWithoutWorkflow.aspx

এবং ক্রমগতভাবে আপনার নিয়মগুলি তৈরি করতে স্টিফেন কাউফম্যানের ওয়েবলগটি দেখুন

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


2

আমি প্রয়োগের জন্য এবং, অথবা নিয়মের মধ্যে আমি ক্লাস রুলএক্সপ্রেস যুক্ত করেছি যা গাছের মূলকে উপস্থাপন করে যা পাতা হতে পারে যা সরল নিয়ম হতে পারে বা হতে পারে এবং বা সেখানে বাইনারি এক্সপ্রেশন থাকে কারণ তাদের নিয়ম নেই এবং এর অভিব্যক্তি নেই:

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
}

আমার আর একটি ক্লাস রয়েছে যা নিয়মের এক্সপ্রেসনটিকে একটিতে সংকলন করে 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);
        }

    }
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.