Kural motoru nasıl uygulanır?


205

Aşağıdaki depolayan bir db tablo var:

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

Şimdi bu kuralların bir koleksiyonuna sahip olduğumu söyleyin:

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

Şimdi bir kullanıcı örneği de var:

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

Nasıl bu kurallar arasında döngü ve mantık uygulamak ve karşılaştırmalar vb gerçekleştirmek?

if(user.age > 15)

if(user.username == "some_name")

Nesnenin 'age' veya 'user_name' gibi özellikleri, 'great_than' ve 'eşit' karşılaştırma işleciyle birlikte tabloda depolandığından, bunu nasıl yapabilirim?

C # statik olarak yazılmış bir dildir, bu yüzden nasıl ilerleyeceğinizden emin değilsiniz.

Yanıtlar:


390

Bu snippet , Kuralları hızlı yürütülebilir kodda ( İfade ağaçlarını kullanarak ) derler ve karmaşık anahtar ifadelerine ihtiyaç duymaz:

(Düzenleme: genel yöntemle tam çalışma örneği )

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

Daha sonra şunları yazabilirsiniz:

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 uygulaması:

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

Ben 'büyük_than' vb yerine 'GreaterThan' kullandığımı unutmayın - bunun nedeni 'GreaterThan' operatör için .NET adı, bu nedenle herhangi bir ekstra harita gerekmez.

Özel adlara ihtiyacınız varsa, çok basit bir sözlük oluşturabilir ve kuralları derlemeden önce tüm operatörleri çevirebilirsiniz:

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

Kod, basitlik için Kullanıcı türünü kullanır. Herhangi bir nesne türü için genel bir Kural derleyicisine sahip olmak için Kullanıcı'yı genel bir T türüyle değiştirebilirsiniz . Ayrıca, kod bilinmeyen operatör adı gibi hataları da işlemelidir.

Anında kod oluşturmanın, Reflection.Emit kullanılarak İfade ağaçları API'sı kullanılmadan önce bile mümkün olduğunu unutmayın. LambdaExpression.Compile () yöntemi, kapakların altında Reflection.Emit kullanır (bunu ILSpy kullanarak görebilirsiniz ).


Sınıfları / nesneleri / vb. Öğrenmek için cevabınız hakkında daha fazla bilgiyi nerede bulabilirim? kodunuzda var mı? Çoğunlukla ifade ağaçları mı?
Blankman

4
Tüm sınıflar System.Linq.Expressions ad alanından gelir ve tümü Expression sınıfının "Expression" türünde fabrika yöntemleri kullanılarak oluşturulur. hepsine erişmek için IDE'nizde. İfade ağaçları hakkında daha fazla bilgiyi buradan edinebilirsiniz msdn.microsoft.com/en-us/library/bb397951.aspx
Martin Konicek

3
@Martin, nitelikli .NET operatör adlarının bir listesini nerede bulabilirim?
Brian Graham

5
@Dark Slipstream Bunları burada bulabilirsiniz msdn.microsoft.com/en-us/library/bb361179.aspx. Hepsi boole ifadeleri değildir - yalnızca boole ifadelerini kullanın (GreaterThan, NotEqual, vb. Gibi).
Martin Konicek

1
@BillDaugherty Üç özelliğe sahip basit bir değer sınıfı kuralı: ÜyeAdı, İşleç, HedefDeğeri. Örneğin, yeni Kural ("Yaş", "Büyükşehir", "20").
Martin Konicek

14

İşte olduğu gibi derleyen ve işi yapan bazı kod. Temel olarak, biri operatör adlarından boole işlevlerine bir eşleme içeren ve diğeri, Kullanıcı türünün özellik adlarından Property Getfos'a (ortaksa) çağırmak için kullanılan PropertyInfos'a bir eşleme içeren iki sözlük kullanın. User örneğini ve üç değeri tablonuzdan statik Apply yöntemine geçirirsiniz.

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

Sorunuzda belirttiğinizden farklı bir yaklaşım gerektiren bir kural motoru oluşturdum, ancak mevcut yaklaşımınızdan çok daha esnek olduğunu düşünüyorum.

Şu anki yaklaşımınız tek bir varlığa odaklanmış gibi görünüyor, "Kullanıcı" ve kalıcı kurallarınız "özeladı", "operatörü" ve "değeri" tanımlıyor. Benim desen yerine bir yüklem için C # kodunu (Func <T, bool>) veritabanımdaki "İfade" sütununda saklar. Geçerli tasarımda, kod oluşturma kullanarak veritabanımdan "kurallar" sorgu ve her biri "Test" yöntemi ile "Kural" türleri ile bir derleme derliyorum. Her kuralı uygulayan arabirimin imzası:

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

"İfade", uygulama ilk çalıştırıldığında "Test" yönteminin gövdesi olarak derlenir. Gördüğünüz gibi, tablodaki diğer sütunlar da kural üzerinde birinci sınıf özellikler olarak ortaya çıkar, böylece bir geliştirici kullanıcının başarısızlık veya başarı hakkında nasıl bilgilendirileceği konusunda bir deneyim oluşturma esnekliğine sahiptir.

Bir bellek içi derleme oluşturmak, uygulamanız sırasında 1 kez gerçekleşir ve kurallarınızı değerlendirirken yansıma kullanmak zorunda kalmadan performans kazanırsınız. Özellik adı yanlış yazılmışsa derleme doğru oluşturulmayacağından, ifadeleriniz çalışma zamanında kontrol edilir.

Bir bellek içi montaj oluşturma mekanizması aşağıdaki gibidir:

  • Kurallarınızı DB'den yükleyin
  • bir StringBuilder ve bazı dize birleştirme kullanarak kurallar ve her biri için yineleme, IDataRule'den devralan bir sınıfı temsil eden Metni yazın
  • CodeDOM kullanarak derleme - daha fazla bilgi

Bu aslında oldukça basittir, çünkü çoğunluk için bu kod, yapıcıdaki özellik uygulamaları ve değer başlatmadır. Bunun yanı sıra, diğer tek kod İfadedir.
Not: CodeDOM bir sınırlama nedeniyle ifadenizin .NET 2.0 (lambdas veya diğer C # 3.0 özellikleri) olması gereken bir sınırlama vardır.

İşte bunun için bazı örnek kod.

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

Bunun ötesinde ICollection> uygulayan "DataRuleCollection" adlı bir sınıf yaptım. Bu, "TestAll" yeteneği ve belirli bir kuralı ada göre yürütmek için bir dizin oluşturucu oluşturmamı sağladı. İşte bu iki yöntemin uygulamaları.

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

DAHA FAZLA KOD: Kod Üretimi ile ilgili kod için bir istek vardı. Ben aşağıda dahil ettim 'RulesAssemblyGenerator' adlı bir sınıftaki işlevselliği kapsüllü.

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

Herhangi varsa diğer ileri kod örnekleri için sorular veya yorumlar veya istekler bana bildirin.


Motorun daha genel hale getirilebileceğini ve CodeDOM API'nın kesinlikle bir seçenek olduğunu haklısınız. Belki çok net olmayan "sb.AppendLine" kodu yerine, CodeDOM'u tam olarak nasıl çağırdığınızı gösterebilirsiniz?
Martin Konicek

8

Yansıma en çok yönlü cevabınızdır. Üç veri sütununuz var ve bunların farklı şekillerde ele alınması gerekiyor:

  1. Alan adınız. Yansıma, kodlanmış alan adından değeri almanın yoludur.

  2. Karşılaştırma operatörünüz. Bunların sınırlı sayıda olması gerektiğinden, bir vaka ifadesi bunları en kolay şekilde ele almalıdır. Özellikle bazıları (biri veya daha fazlası var) biraz daha karmaşık olduğu için.

  3. Karşılaştırma değeriniz. Bunların hepsi düz değerlerse, çoklu girişleri bölmeniz gerekse de bu kolaydır. Ancak, alan adları da olsa yansıma kullanabilirsiniz.

Daha çok şöyle bir yaklaşım benimseyeceğim:

    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)

vs vs.

Karşılaştırma için daha fazla seçenek ekleme esnekliği sağlar. Ayrıca, Karşılaştırma yöntemleri içinde istediğiniz herhangi bir tür doğrulamasını kodlayabileceğiniz ve istediğiniz kadar karmaşık hale getirebileceğiniz anlamına gelir. Burada CompareTo'nun başka bir satıra tekrarlanan çağrı olarak veya aşağıdaki gibi yapılabilecek bir alan değeri olarak değerlendirilmesi seçeneği de vardır:

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

Her şey gelecek için olasılıklara bağlıdır ....


Ve yansıtılan montajlarınızı / nesnelerinizi önbelleğe alabilirsiniz, bu da kodunuzu daha da performanslandıracaktır.
Mrchief

7

Sadece bir avuç mülkünüz ve operatörünüz varsa, en az dirençli yol, tüm kontrolleri aşağıdaki gibi özel durumlar olarak kodlamaktır:

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

Çok fazla özelliğiniz varsa, tablo odaklı bir yaklaşımı daha lezzetli bulabilirsiniz. Bu durumda Dictionary, özellik adlarını, örneğin, eşleşen delegelere eşleyen bir statik oluşturabilirsiniz Func<User, object>.

Derleme zamanında özelliklerin adlarını bilmiyorsanız veya her özellik için özel durumlardan kaçınmak ve tablo yaklaşımını kullanmak istemiyorsanız, özellikleri almak için yansımayı kullanabilirsiniz. Örneğin:

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

Ancak TargetValue, muhtemelen a olduğundan string, gerekirse kurallar tablosundan tür dönüşümü yapmaya dikkat etmeniz gerekir.


value.CompareTo (limit) ne döndürür? -1 0 veya 1? B4'ü görmedim!
Blankman

1
@Blankman: Kapat: sıfırdan küçük, sıfır veya sıfırdan büyük. IComparableşeyleri karşılaştırmak için kullanılır. İşte dokümanlar: IComparable.CompareTo Yöntemi .
Rick Sladkey

2
Bu cevabın neden yüksek oy aldığını anlamıyorum. Birçok tasarım ilkesini ihlal eder: "Söyle sorma" => kuralların her birinin bir sonuç döndürmesi istenmelidir . "Uzantı için açık / değişiklik için kapalı" => herhangi bir yeni kural ApplyRules yönteminin değişiklik gerektirdiği anlamına gelir. Ayrıca, kodu bir bakışta anlamak zordur.
Appetere

2
Gerçekten de, en az direnç yolu nadiren en iyi yoldur. Lütfen mükemmel ifade ağacı yanıtını görün ve onaylayın.
Rick Sladkey

6

Bir genişletme yöntemiyle veri türü odaklı bir yaklaşım hakkında ne söyleyebiliriz:

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

Bundan böyle değerlendirebilirsiniz:

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

4

"Bir kural motoru nasıl uygulanır? (C #)" sorusunu yanıtlamanın en belirgin yolu, belirli bir kural dizisini sırayla yürütmek olsa da, bu genellikle naif bir uygulama olarak kabul edilir (işe yaramadığı anlamına gelmez :-)

Durumunuzda "yeterince iyi" gibi görünüyor çünkü probleminiz "bir dizi kuralı nasıl çalıştıracağınız" gibi görünüyor ve lambda / ifade ağacı (Martin'in cevabı) kesinlikle bu konuda en zarif yol en son C # sürümleriyle donatılmıştır.

Bununla birlikte, daha gelişmiş senaryolar için, aslında birçok ticari kural motoru sisteminde uygulanan Rete Algoritması'na ve C # 'da bu algoritmanın bir uygulaması olan NRuler'e bir başka bağlantı bulunmaktadır .


3

Martin'in cevabı oldukça iyiydi. Aslında onunla aynı fikri olan bir kural motoru yaptım. Ve neredeyse aynı olduğuna şaşırdım. Kodunu biraz iyileştirmek için dahil ettim. Her ne kadar daha karmaşık kurallarla başa çıksam da.

Sen bakabilirsiniz Yare.NET

Veya Nuget'ten indirin



2

Ben ve kurallar arasında uygulama ekledi basit bir kurala yaprak olabilir bir ağacın kökünü temsil sınıf RuleExpression ekledi veya olabilir ve ya da orada kural yok ve ifadeleri var için ikili ifadeler:

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
}

Bir tane kuralExpression derlemek başka bir sınıf var 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);
        }

    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.