Come implementare un motore di regole?


205

Ho una tabella db che memorizza quanto segue:

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

Ora dì che ho una raccolta di queste regole:

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

Ora ho anche un'istanza di un utente:

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

Come potrei scorrere queste regole, applicare la logica ed eseguire i confronti ecc.?

if(user.age > 15)

if(user.username == "some_name")

Poiché la proprietà dell'oggetto come 'age' o 'user_name' è memorizzata nella tabella, insieme all'operatore di confronto 'great_than' e 'uguale', come potrei farlo?

C # è un linguaggio tipicamente statico, quindi non sei sicuro di come andare avanti.

Risposte:


391

Questo frammento compila le Regole in un codice eseguibile rapido (usando gli alberi delle espressioni ) e non necessita di istruzioni switch complicate:

(Modifica: esempio di lavoro completo con metodo generico )

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

È quindi possibile scrivere:

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

Ecco l'implementazione di 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);
    }
}

Nota che ho usato 'GreaterThan' invece di 'greater_than' ecc. - Questo perché 'GreaterThan' è il nome .NET per l'operatore, quindi non abbiamo bisogno di alcun mapping aggiuntivo.

Se hai bisogno di nomi personalizzati, puoi creare un dizionario molto semplice e tradurre tutti gli operatori prima di compilare le regole:

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

Il codice utilizza il tipo Utente per semplicità. È possibile sostituire l'utente con un tipo T generico per avere un compilatore di regole generico per qualsiasi tipo di oggetto. Inoltre, il codice dovrebbe gestire errori, come il nome dell'operatore sconosciuto.

Si noti che la generazione di codice al volo era possibile anche prima dell'introduzione dell'API degli alberi delle espressioni, tramite Reflection.Emit. Il metodo LambdaExpression.Compile () usa Reflection.Emit sotto le copertine (puoi vederlo usando ILSpy ).


Dove posso leggere di più sulla tua risposta per imparare le classi / oggetti / ecc. hai nel tuo nel tuo codice? Sono soprattutto alberi di espressione?
Blankman,

4
Tutte le classi provengono dallo spazio dei nomi System.Linq.Expressions e tutte vengono create utilizzando i metodi di fabbrica della classe Expression - digitare "Expression". nel tuo IDE per accedervi tutti. Maggiori informazioni sugli alberi delle espressioni qui msdn.microsoft.com/en-us/library/bb397951.aspx
Martin Konicek,

3
@Martin dove posso trovare un elenco di nomi di operatori .NET qualificati?
Brian Graham,

5
@Dark Slipstream Puoi trovarli qui msdn.microsoft.com/en-us/library/bb361179.aspx. Non tutte sono espressioni booleane: usa solo quelle booleane (come GreaterThan, NotEqual, ecc.).
Martin Konicek,

1
@BillDaugherty Regola una semplice classe di valori con tre proprietà: MemberName, Operator, TargetValue. Ad esempio, nuova regola ("Età", "Maggiore", "20").
Martin Konicek,

14

Ecco del codice che viene compilato così com'è e fa il lavoro. Fondamentalmente utilizzare due dizionari, uno contenente una mappatura dai nomi degli operatori alle funzioni booleane e un altro contenente una mappa dai nomi delle proprietà del tipo Utente a PropertyInfos utilizzato per invocare il getter della proprietà (se pubblico). Si passa l'istanza utente e i tre valori dalla tabella al metodo Apply statico.

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

Ho creato un motore di regole che adotta un approccio diverso rispetto a quello delineato nella tua domanda, ma penso che lo troverai molto più flessibile del tuo attuale approccio.

Il tuo approccio attuale sembra focalizzato su una singola entità, "Utente", e le tue regole persistenti identificano "nome proprietà", "operatore" e "valore". Il mio modello, invece, memorizza il codice C # per un predicato (Func <T, bool>) in una colonna "Espressione" nel mio database. Nel progetto attuale, usando la generazione del codice, sto interrogando le "regole" dal mio database e compilando un assembly con tipi "Rule", ognuno con un metodo "Test". Ecco la firma per l'interfaccia implementata in ogni regola:

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

"Expression" viene compilato come corpo del metodo "Test" quando l'applicazione viene eseguita per la prima volta. Come puoi vedere, le altre colonne nella tabella sono anche emerse come proprietà di prima classe sulla regola in modo che uno sviluppatore abbia la flessibilità di creare un'esperienza su come l'utente viene avvisato di errori o esiti positivi.

La generazione di un assembly in memoria avviene una volta sola durante l'applicazione e si ottiene un miglioramento delle prestazioni non dovendo utilizzare la riflessione durante la valutazione delle regole. Le tue espressioni vengono verificate in fase di esecuzione poiché l'assembly non verrà generato correttamente se il nome di una proprietà è errato, ecc.

I meccanismi di creazione di un assembly in memoria sono i seguenti:

  • Carica le tue regole dal DB
  • iterare le regole e per ciascuna, usando StringBuilder e qualche concatenazione di stringhe scrivere il testo che rappresenta una classe che eredita da IDataRule
  • compilare usando CodeDOM - maggiori informazioni

Questo è in realtà abbastanza semplice perché per la maggior parte questo codice è implementazioni di proprietà e inizializzazione di valore nel costruttore. Oltre a ciò, l'unico altro codice è l'Espressione.
NOTA: esiste una limitazione per cui l'espressione deve essere .NET 2.0 (nessuna lambda o altre funzionalità di C # 3.0) a causa di una limitazione in CodeDOM.

Ecco un codice di esempio per questo.

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

Oltre a ciò ho creato una classe che ho chiamato "DataRuleCollection", che ha implementato ICollection>. Ciò mi ha permesso di creare una funzionalità "TestAll" e un indicizzatore per l'esecuzione di una regola specifica per nome. Ecco le implementazioni per questi due metodi.

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

ALTRO CODICE: è stata richiesta una richiesta di codice relativa alla generazione del codice. Ho incapsulato la funzionalità in una classe chiamata 'RulesAssemblyGenerator' che ho incluso di seguito.

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

Se ci sono altre domande o commenti o richieste per ulteriori esempi di codice, fammi sapere.


Hai ragione sul fatto che il motore può essere reso più generico e l'API CodeDOM è sicuramente anche un'opzione. Forse invece del codice "sb.AppendLine" che non è molto chiaro, potresti mostrare come invocare esattamente CodeDOM?
Martin Konicek,

8

La riflessione è la tua risposta più versatile. Hai tre colonne di dati e devono essere trattate in diversi modi:

  1. Il nome del tuo campo. La riflessione è il modo per ottenere il valore da un nome campo codificato.

  2. Il tuo operatore di confronto. Dovrebbe esserci un numero limitato di questi, quindi un'istruzione case dovrebbe gestirli più facilmente. Soprattutto perché alcuni di essi (ne hanno uno o più) sono leggermente più complessi.

  3. Il tuo valore di confronto. Se questi sono tutti valori semplici, allora è facile, anche se avrai diviso le voci multiple in alto. Tuttavia, puoi anche utilizzare la riflessione se sono anche nomi di campo.

Vorrei un approccio più simile a:

    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)

ecc ecc.

Ti dà la flessibilità per aggiungere più opzioni per il confronto. Significa anche che è possibile codificare nei metodi di confronto qualsiasi tipo di convalida che si desidera e renderli complessi come si desidera. C'è anche l'opzione qui per il CompareTo da valutare come una chiamata ricorsiva a un'altra linea, o come un valore di campo, che potrebbe essere fatto come:

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

Tutto dipende dalle possibilità per il futuro ....


E puoi memorizzare nella cache i tuoi assembly / oggetti riflessi che renderanno il tuo codice ancora più performante.
Mrchief,

7

Se hai solo una manciata di proprietà e operatori, il percorso del minimo di resistenza è semplicemente codificare tutti i controlli come casi speciali come questo:

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

Se hai molte proprietà, potresti trovare un approccio guidato da una tabella più appetibile. In tal caso, si creerebbe una statica Dictionaryche associ i nomi delle proprietà ai delegati corrispondenti, per esempio Func<User, object>.

Se non si conoscono i nomi delle proprietà al momento della compilazione o si desidera evitare casi speciali per ciascuna proprietà e non si desidera utilizzare l'approccio della tabella, è possibile utilizzare la riflessione per ottenere le proprietà. Per esempio:

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

Ma poiché TargetValueè probabilmente un string, dovrai fare attenzione a fare la conversione del tipo dalla tabella delle regole, se necessario.


cosa restituisce value.CompareTo (limit)? -1 0 o 1? Non ho visto quel b4!
Blankman,

1
@Blankman: Close: minore di zero, zero o maggiore di zero. IComparableè usato per confrontare le cose. Ecco i documenti: Metodo IComparable.CompareTo .
Rick Sladkey,

2
Non capisco perché questa risposta sia stata votata per eccesso. Infrange molti principi di progettazione: "Tell don't ask" => le regole dovrebbero essere richieste a ciascuno di restituire un risultato. "Apri per estensione / chiuso per modifica" => qualsiasi nuova regola indica che il metodo ApplyRules deve essere modificato. Inoltre, il codice è difficile da capire a colpo d'occhio.
Appetere,

2
In effetti, il percorso di minor resistenza è raramente il percorso migliore. Si prega di vedere e valutare l'eccellente risposta dell'albero delle espressioni.
Rick Sladkey,

6

Che dire di un approccio orientato al tipo di dati con un metodo di estensione:

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

Di quanto puoi evacuare in questo modo:

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

4

Sebbene il modo più ovvio per rispondere alla domanda "Come implementare un motore di regole? (In C #)" sia eseguire un determinato insieme di regole in sequenza, questo è generalmente considerato come un'implementazione ingenua (non significa che non funziona :-)

Sembra che sia "abbastanza buono" nel tuo caso perché il tuo problema sembra essere più "come eseguire un insieme di regole in sequenza", e l'albero lambda / espressione (la risposta di Martin) è sicuramente il modo più elegante in quella materia se tu sono equipaggiati con versioni recenti di C #.

Tuttavia, per scenari più avanzati, ecco un link all'algoritmo di rete che è in realtà implementato in molti sistemi di motori di regole commerciali e un altro link a NRuler , un'implementazione di tale algoritmo in C #.


3

La risposta di Martin fu abbastanza buona. In realtà ho creato un motore di regole che ha la stessa idea della sua. E sono stato sorpreso che sia quasi lo stesso. Ho incluso parte del suo codice per migliorarlo in qualche modo. Anche se l'ho fatto per gestire regole più complesse.

Puoi guardare Yare.NET

O scaricalo in Nuget



2

Ho aggiunto l'implementazione per e, o tra le regole, ho aggiunto la classe RuleExpression che rappresenta la radice di un albero che può essere foglia la regola semplice o può essere e, o espressioni binarie lì perché non hanno regola e hanno espressioni:

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
}

Ho un'altra classe che compila la ruleExpression in una 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);
        }

    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.