webentwicklung-frage-antwort-db.com.de

Wie implementiere ich eine Regel-Engine?

Ich habe eine DB-Tabelle, in der Folgendes gespeichert ist:

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

Angenommen, ich habe eine Sammlung dieser Regeln:

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

Jetzt habe ich eine Instanz eines Benutzers auch:

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

Wie würde ich diese Regeln durchlaufen und die Logik anwenden und die Vergleiche usw. durchführen?

if(user.age > 15)

if(user.username == "some_name")

Wie kann ich das tun, da die Eigenschaft des Objekts wie 'age' oder 'user_name' zusammen mit dem Vergleichsoperator 'great_than' und 'equal' in der Tabelle gespeichert ist?

C # ist eine statisch typisierte Sprache, daher sind wir uns nicht sicher, wie wir vorgehen sollen.

191
Blankman

Dieses Snippet kompiliert die Regeln in schnell ausführbaren Code (unter Verwendung von Ausdrucksbäumen ) und benötigt keine komplizierten switch-Anweisungen:

(Edit: voll funktionsfähiges Beispiel mit generischer Methode )

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

Sie können dann schreiben:

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

Hier ist die Implementierung von 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);
    }
}

Beachten Sie, dass ich 'GreaterThan' anstelle von 'larger_than' usw. verwendet habe. Dies liegt daran, dass 'GreaterThan' der .NET-Name für den Operator ist. Daher benötigen wir keine zusätzliche Zuordnung.

Wenn Sie benutzerdefinierte Namen benötigen, können Sie ein sehr einfaches Wörterbuch erstellen und einfach alle Operatoren übersetzen, bevor Sie die Regeln kompilieren:

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

Der Code verwendet der Einfachheit halber den Typ Benutzer. Sie können User durch einen generischen Typ T ersetzen, um einen generischen Regel-Compiler für alle Arten von Objekten zu erhalten. Außerdem sollte der Code Fehler behandeln, wie z. B. den Namen eines unbekannten Operators.

Beachten Sie, dass das Generieren von Code im laufenden Betrieb bereits möglich war, bevor die Expression-Tree-API mit Reflection.Emit eingeführt wurde. Die Methode LambdaExpression.Compile () verwendet Reflection.Emit unter dem Deckmantel (Sie können dies mit ILSpy sehen).

371
Martin Konicek

Hier ist ein Code, der so kompiliert wird, wie er ist und die Arbeit erledigt. Verwenden Sie im Allgemeinen zwei Wörterbücher, von denen eines eine Zuordnung von Operatornamen zu Booleschen Funktionen und das andere eine Zuordnung von den Eigenschaftsnamen des Benutzertyps zu PropertyInfos enthält, die zum Aufrufen des Eigenschafts-Getter (falls öffentlich) verwendet werden. Sie übergeben die Benutzerinstanz und die drei Werte aus Ihrer Tabelle an die statische Apply-Methode.

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

Ich habe eine Regel-Engine erstellt, die einen anderen Ansatz verfolgt als Sie in Ihrer Frage beschrieben, aber ich denke, Sie werden feststellen, dass sie viel flexibler ist als Ihr aktueller Ansatz.

Ihr aktueller Ansatz scheint sich auf eine einzelne Entität, "Benutzer", zu konzentrieren, und Ihre beständigen Regeln identifizieren "Eigenschaftsname", "Operator" und "Wert". Mein Muster speichert stattdessen den C # -Code für ein Prädikat (Func <T, bool>) in einer Spalte "Ausdruck" in meiner Datenbank. Im aktuellen Entwurf frage ich mithilfe der Codegenerierung die "Regeln" aus meiner Datenbank ab und kompiliere eine Assembly mit "Regeltypen", jeweils mit einer "Test" -Methode. Hier ist die Signatur für die Schnittstelle, die in jeder Regel implementiert ist:

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

Der "Ausdruck" wird als Hauptteil der "Test" -Methode kompiliert, wenn die Anwendung zum ersten Mal ausgeführt wird. Wie Sie sehen, werden auch die anderen Spalten in der Tabelle als erstklassige Eigenschaften in der Regel angezeigt, sodass ein Entwickler flexibel festlegen kann, wie der Benutzer über Fehler oder Erfolg benachrichtigt wird.

Das Generieren einer speicherinternen Assembly ist ein einmaliges Ereignis in Ihrer Anwendung, und Sie erhalten einen Leistungsgewinn, wenn Sie bei der Bewertung Ihrer Regeln keine Reflektion verwenden müssen. Ihre Ausdrücke werden zur Laufzeit überprüft, da die Assembly nicht korrekt generiert wird, wenn ein Eigenschaftsname falsch geschrieben ist.

Die Mechanismen zum Erstellen einer In-Memory-Assembly lauten wie folgt:

  • Laden Sie Ihre Regeln aus der DB
  • durchlaufen Sie die Regeln und verwenden Sie für jede Zeichenfolge einen StringBuilder und einige Zeichenfolgenverkettungen, um den Text zu schreiben, der eine Klasse darstellt, die von IDataRule erbt
  • kompilieren Sie mit CodeDOM - weitere Informationen

Dies ist eigentlich recht einfach, da es sich bei diesem Code zum größten Teil um Eigenschaftsimplementierungen und Wertinitialisierungen im Konstruktor handelt. Abgesehen davon ist der einzige andere Code der Ausdruck.
ANMERKUNG: Es gibt eine Einschränkung, dass Ihr Ausdruck .NET 2.0 sein muss (keine Lambdas oder andere C # 3.0-Funktionen), da CodeDOM eingeschränkt ist.

Hier ist ein Beispielcode dafür.

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

Darüber hinaus habe ich eine Klasse namens "DataRuleCollection" erstellt, die ICollection> implementiert. Dadurch konnte ich eine "TestAll" -Funktion und einen Indexer zum Ausführen einer bestimmten Regel nach Namen erstellen. Hier sind die Implementierungen für diese beiden Methoden.

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

MEHR CODE: Es gab eine Anfrage für den Code im Zusammenhang mit der Codegenerierung. Ich habe die Funktionalität in einer Klasse namens 'RulesAssemblyGenerator' zusammengefasst, die ich unten angegeben habe.

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

Wenn Sie other Fragen, Kommentare oder Wünsche zu weiteren Codebeispielen haben, lassen Sie es mich wissen.

9
Glenn Ferrie

Reflexion ist Ihre vielseitigste Antwort. Sie haben drei Datenspalten, die unterschiedlich behandelt werden müssen:

  1. Ihr Feldname. Reflexion ist der Weg, um den Wert aus einem codierten Feldnamen zu erhalten.

  2. Ihr Vergleichsoperator. Es sollte eine begrenzte Anzahl von diesen geben, so dass eine case-Anweisung sie am einfachsten handhaben sollte. Zumal einige von ihnen (hat eine oder mehrere) etwas komplexer sind.

  3. Ihr Vergleichswert. Wenn dies alles gerade Werte sind, ist dies einfach, obwohl Sie die mehreren Einträge aufgeteilt haben. Sie können jedoch auch die Reflektion verwenden, wenn es sich auch um Feldnamen handelt.

Ich würde eher wie folgt vorgehen:

    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)

usw. usw.

Es gibt Ihnen Flexibilität beim Hinzufügen weiterer Vergleichsoptionen. Dies bedeutet auch, dass Sie innerhalb der Compare-Methoden jede gewünschte Typvalidierung codieren und sie so komplex gestalten können, wie Sie möchten. Hier besteht auch die Möglichkeit, dass CompareTo als rekursiver Rückruf auf eine andere Zeile oder als Feldwert ausgewertet wird. Dies kann folgendermaßen erfolgen:

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

Auf die Zukunftsmöglichkeiten kommt es an ....

8

Wenn Sie nur eine Handvoll Eigenschaften und Operatoren haben, besteht der Weg des geringsten Widerstands darin, alle Prüfungen als Sonderfälle wie folgt zu codieren:

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

Wenn Sie über viele Eigenschaften verfügen, ist ein tabellenbasierter Ansatz möglicherweise besser geeignet. In diesem Fall würden Sie ein statisches Dictionary erstellen, das Eigenschaftsnamen Delegaten zuordnet, die beispielsweise Func<User, object>.

Wenn Sie die Namen der Eigenschaften zum Zeitpunkt der Kompilierung nicht kennen oder Sonderfälle für jede Eigenschaft vermeiden und den Tabellenansatz nicht verwenden möchten, können Sie mithilfe von Reflection Eigenschaften abrufen. Beispielsweise:

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

Da TargetValue wahrscheinlich ein string ist, müssen Sie bei Bedarf darauf achten, die Typkonvertierung aus der Regeltabelle durchzuführen.

7
Rick Sladkey

Was ist mit einem datentyporientierten Ansatz mit einer Erweiterungsmethode:

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

Dann können Sie wie folgt auswerten:

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

Obwohl die naheliegendste Möglichkeit zur Beantwortung der Frage "Implementieren einer Regelengine (in C #)" darin besteht, einen bestimmten Satz von Regeln nacheinander auszuführen, wird dies im Allgemeinen als naive Implementierung angesehen (was nicht bedeutet, dass dies nicht funktioniert) :-)

Es scheint in Ihrem Fall "gut genug" zu sein, weil Ihr Problem eher darin zu bestehen scheint, "wie ein Satz von Regeln nacheinander abgearbeitet wird", und der Lambda/Ausdrucksbaum (Martins Antwort) ist in dieser Hinsicht sicherlich die eleganteste Methode sind mit aktuellen C # -Versionen ausgestattet.

Für fortgeschrittenere Szenarien finden Sie hier jedoch einen Link zu Rete-Algorithmus , der in vielen kommerziellen Regel-Engine-Systemen implementiert ist, und einen weiteren Link zu NRuler , einer Implementierung davon Algorithmus in C #.

4
Simon Mourier

Martins Antwort war ziemlich gut. Ich habe tatsächlich eine Regel-Engine erstellt, die die gleiche Idee hat wie seine. Und ich war überrascht, dass es fast dasselbe ist. Ich habe einen Teil seines Codes eingefügt, um ihn etwas zu verbessern. Obwohl ich es geschafft habe, mit komplexeren Regeln umzugehen.

Sie können sich Yare.NET ansehen

Oder laden Sie es in Nuget

3
aiapatag

Wie wäre es mit der Workflow-Regel-Engine?

Sie können Windows-Workflowregeln ohne Workflow ausführen, siehe Guy Bursteins Blog: http://blogs.Microsoft.co.il/blogs/bursteg/archive/2006/10/11/RuleExecutionWithoutWorkflow.aspx

informationen zum programmgesteuerten Erstellen Ihrer Regeln finden Sie im WebLog von Stephen Kaufman

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

2
Kevin Burton

Ich habe eine Implementierung für und oder zwischen Regeln hinzugefügt. Ich habe die Klasse RuleExpression hinzugefügt, die die Wurzel eines Baums darstellt, der ein Blatt sein kann. Die Regel ist einfach, und kann es sein, oder binäre Ausdrücke, da sie keine Regeln und Ausdrücke haben:

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
}

Ich habe eine andere Klasse, die den ruleExpression zu einem kompiliert 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

Ich habe ein Problem mit der Groß- und Kleinschreibung in der Antwort von Martin Konicek. Wenn Sie also möchten, dass bei rule.MemberName Nicht zwischen Groß- und Kleinschreibung unterschieden wird, fügen Sie einfach hinzu

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