Como implementar um mecanismo de regras?

205

Eu tenho uma tabela db que armazena o seguinte:

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

Agora diga que tenho uma coleção dessas regras:

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

Agora também tenho uma instância de um usuário:

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

Como eu percorreria essas regras e aplicaria a lógica e executaria as comparações etc.?

if(user.age > 15)

if(user.username == "some_name")

Como a propriedade do objeto, como 'age' ou 'user_name', é armazenada na tabela, junto com o operador de comparação 'great_than' e 'equal', como eu poderia fazer isso?

C # é uma linguagem de tipo estaticamente, portanto, não sei como avançar.

Blankman
fonte

Respostas:

391

Esse trecho compila as regras em código executável rápido (usando árvores de expressão ) e não precisa de nenhuma instrução de chave complicada:

(Editar: exemplo de trabalho completo com método genérico )

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

Você pode então escrever:

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

Aqui está a implementação do 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);
    }
}

Observe que eu usei 'GreaterThan' em vez de 'Greater_than' etc. etc. - isso ocorre porque 'GreaterThan' é o nome do .NET para o operador, portanto, não precisamos de nenhum mapeamento extra.

Se você precisar de nomes personalizados, crie um dicionário muito simples e traduza todos os operadores antes de compilar as regras:

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

O código usa o tipo Usuário para simplificar. Você pode substituir Usuário por um tipo genérico T para ter um compilador de Regras genérico para qualquer tipo de objeto. Além disso, o código deve manipular erros, como nome de operador desconhecido.

Observe que a geração rápida de código era possível mesmo antes da API de árvores de expressão ser introduzida, usando Reflection.Emit. O método LambdaExpression.Compile () usa Reflection.Emit nos bastidores (você pode ver isso usando ILSpy ).

Martin Konicek
fonte
Onde posso ler mais sobre sua resposta para aprender as classes / objetos / etc. você tem no seu código? É principalmente árvores de expressão?
Blankman
4
Todas as classes vêm do espaço de nome System.Linq.Expressions e todas são criadas usando os métodos de fábrica da classe Expression - digite "Expression". no seu IDE para acessar todos eles. Leia mais sobre as árvores de expressão aqui msdn.microsoft.com/en-us/library/bb397951.aspx
Martin
3
@ Martin, onde posso encontrar uma lista de nomes de operadores .NET qualificados?
Brian Graham
5
@ Slipstream escuro Você pode encontrá-los aqui msdn.microsoft.com/en-us/library/bb361179.aspx. Nem todas são expressões booleanas - use apenas as expressões booleanas (como GreaterThan, NotEqual etc.).
Martin Konicek
1
@BillDaugherty Regra uma classe de valor simples com três propriedades: MemberName, Operator, TargetValue. Por exemplo, nova regra ("Age", "GreaterThan", "20").
Martin Konicek
14

Aqui está um código que compila como está e faz o trabalho. Basicamente, use dois dicionários, um contendo um mapeamento de nomes de operadores para funções booleanas e outro contendo um mapa dos nomes de propriedades do tipo Usuário para PropertyInfos usado para chamar o getter de propriedade (se público). Você passa a instância do usuário e os três valores da sua tabela para o método Apply estático.

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

Criei um mecanismo de regras que adota uma abordagem diferente da descrita na sua pergunta, mas acho que você achará muito mais flexível do que sua abordagem atual.

Sua abordagem atual parece estar focada em uma única entidade, "Usuário", e suas regras persistentes identificam "nome da propriedade", "operador" e "valor". Meu padrão, em vez disso, armazena o código C # para um predicado (Func <T, bool>) em uma coluna "Expressão" no meu banco de dados. No design atual, usando a geração de código, estou consultando as "regras" do meu banco de dados e compilando um assembly com os tipos "Rule", cada um com o método "Test". Aqui está a assinatura da interface implementada em cada regra:

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

A "Expressão" é compilada como o corpo do método "Teste" quando o aplicativo é executado pela primeira vez. Como você pode ver, as outras colunas da tabela também são exibidas como propriedades de primeira classe na regra, para que um desenvolvedor tenha flexibilidade para criar uma experiência de como o usuário é notificado sobre falha ou sucesso.

A geração de um assembly na memória é uma ocorrência única durante o aplicativo e você obtém um ganho de desempenho por não precisar usar a reflexão ao avaliar suas regras. Suas expressões são verificadas em tempo de execução, pois o assembly não será gerado corretamente se um nome de propriedade estiver incorreto, etc.

Os mecanismos de criação de um assembly na memória são os seguintes:

  • Carregue suas regras do banco de dados
  • itere sobre as regras e para cada um, usando um StringBuilder e alguma concatenação de strings, escreva o Text representando uma classe que herda de IDataRule
  • compilar usando o CodeDOM - mais informações

Na verdade, isso é bastante simples porque, para a maioria, esse código é implementações de propriedades e inicialização de valor no construtor. Além disso, o único outro código é a expressão.
NOTA: existe uma limitação de que sua expressão deve ser .NET 2.0 (sem lambdas ou outros recursos do C # 3.0) devido a uma limitação no CodeDOM.

Aqui está um código de exemplo para isso.

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

Além disso, criei uma classe chamada "DataRuleCollection", que implementou o ICollection>. Isso me permitiu criar um recurso "TestAll" e um indexador para executar uma regra específica por nome. Aqui estão as implementações para esses dois métodos.

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

MAIS CÓDIGO: Houve uma solicitação para o código relacionado à Geração de Código. Encapsulei a funcionalidade em uma classe chamada 'RulesAssemblyGenerator' que incluí abaixo.

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 houver outras perguntas, comentários ou solicitações de mais amostras de código, entre em contato.

Glenn Ferrie
fonte
Você está certo de que o mecanismo pode se tornar mais genérico e a API CodeDOM também é definitivamente uma opção. Talvez, em vez do código "sb.AppendLine", que não é muito claro, você possa mostrar como exatamente você chama o CodeDOM?
Martin Konicek
8

A reflexão é a sua resposta mais versátil. Você tem três colunas de dados e elas precisam ser tratadas de maneiras diferentes:

  1. O seu nome de campo. Reflexão é a maneira de obter o valor de um nome de campo codificado.

  2. O seu operador de comparação. Deve haver um número limitado deles, portanto, uma declaração de caso deve lidar com eles mais facilmente. Especialmente porque alguns deles (tem um ou mais deles) são um pouco mais complexos.

  3. O seu valor de comparação. Se todos esses valores são retos, isso é fácil, embora você tenha dividido as várias entradas acima. No entanto, você também pode usar a reflexão se eles também forem nomes de campos.

Eu adotaria uma abordagem mais como:

    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)

etc etc.

Dá flexibilidade para adicionar mais opções de comparação. Isso também significa que você pode codificar nos métodos de comparação qualquer validação de tipo que desejar e torná-los tão complexos quanto desejar. Há também a opção aqui para que o CompareTo seja avaliado como uma chamada recursiva de volta para outra linha ou como um valor de campo, o que poderia ser feito como:

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

Tudo depende das possibilidades para o futuro ....

Gato Schroedingers
fonte
E você pode armazenar em cache seus assemblies / objetos refletidos, o que tornará seu código ainda mais eficiente.
Mrchief
7

Se você possui apenas algumas propriedades e operadores, o caminho de menor resistência é apenas codificar todas as verificações como casos especiais como este:

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 você tiver muitas propriedades, poderá achar uma abordagem orientada a tabelas mais agradável. Nesse caso, você criaria uma estática Dictionaryque mapeia nomes de propriedades para delegados correspondentes, digamos Func<User, object>,.

Se você não souber os nomes das propriedades no momento da compilação, ou se desejar evitar casos especiais para cada propriedade e não quiser usar a abordagem de tabela, poderá usar a reflexão para obter propriedades. Por exemplo:

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

Mas como TargetValueprovavelmente é um string, você precisará fazer a conversão de tipos da tabela de regras, se necessário.

Rick Sladkey
fonte
o que value.CompareTo (limite) retorna? -1 0 ou 1? Não vi esse b4!
Blankman
1
@ Blankman: Close: menor que zero, zero ou maior que zero. IComparableé usado para comparar coisas. Aqui estão os documentos: Método IComparable.CompareTo .
Rick Sladkey
2
Não entendo por que essa resposta foi votada com êxito. Ele viola muitos princípios de design: "Diga não pergunte" => as regras devem ser solicitadas para retornar um resultado. "Aberto para extensão / fechado para modificação" => qualquer nova regra significa que o método ApplyRules precisa de modificação. Além disso, é difícil entender rapidamente o código.
Appetere
2
De fato, o caminho de menor resistência raramente é o melhor caminho. Por favor, veja e vote na excelente resposta da árvore de expressões.
Rick Sladkey
6

Que tal uma abordagem orientada ao tipo de dados com um método de extensão:

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

Do que você pode avaliar assim:

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

Embora a maneira mais óbvia de responder à pergunta "Como implementar um mecanismo de regras? (Em C #)" seja executar um determinado conjunto de regras em sequência, isso geralmente é considerado uma implementação ingênua (não significa que não funcione). :-)

Parece que é "bom o suficiente" no seu caso, porque seu problema parece ser "como executar um conjunto de regras em sequência", e a árvore lambda / expression (resposta de Martin) é certamente a maneira mais elegante nesse assunto, se você são equipados com versões recentes do C #.

No entanto, para cenários mais avançados, aqui está um link para o algoritmo Rete, que é de fato implementado em muitos sistemas de mecanismo de regras comerciais, e outro link para NRuler , uma implementação desse algoritmo em C #.

Simon Mourier
fonte
3

A resposta de Martin foi muito boa. Na verdade, criei um mecanismo de regras que tem a mesma ideia que a dele. E fiquei surpreso que é quase o mesmo. Incluí parte do seu código para melhorá-lo. Embora eu tenha feito isso para lidar com regras mais complexas.

Você pode olhar para o Yare.NET

Ou faça o download em Nuget

aiapatag
fonte
2

Eu adicionei a implementação para e, ou entre regras, adicionei a classe RuleExpression que representa a raiz de uma árvore que pode ser folha a regra é simples ou pode ser e, ou expressões binárias lá porque elas não têm regra e têm expressões:

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
}

Eu tenho outra classe que compila o ruleExpression para um 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);
        }

    }
Max.Futerman
fonte