Construire un MicroRuleEngine avec LinqExpressions

.net c# expression-trees linq

Question

Je construis donc un MicroRuleEngine (j'aimerais voir cela décoller en tant que projet OpenSource) et je rencontre une erreur de référence null lors de l'exécution de ExpressionTree compilé et je ne sais pas trop pourquoi. Les règles relatives aux propriétés simples fonctionnent, mais elles vont à l'encontre des propriétés enfants, telles que Customer.Client.Address.StreetName, etc.

Ci-dessous, le MicroRuleEngine

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace Trial
{
    public class MicroRuleEngine
    {
        public bool PassesRules<T>(List<Rule> rules, T toInspect)
        {
            bool pass = true;
            foreach (var rule in rules)
            {
                var cr = this.CompileRule<T>(rule);
                pass = pass && cr.Invoke(toInspect);
                if (!pass)
                    return pass;
            }
            return pass;
        }
        public Func<T, bool> CompileRule<T>(Rule r)
        {
            var paramUser = Expression.Parameter(typeof(T));
            Expression expr = BuildExpr<T>(r, paramUser);
            // build a lambda function User->bool and compile it

            return Expression.Lambda<Func<T, bool>>(expr, paramUser).Compile();
        }

        Expression BuildExpr<T>(Rule r, ParameterExpression param)
        {
            Expression propExpression;
            Type propType;// typeof(T).GetProperty(r.MemberName).PropertyType;
            ExpressionType tBinary;
            if (r.MemberName.Contains('.'))
            {
                // support to be sorted on child fields.
                String[] childProperties = r.MemberName.Split('.');
                var property = typeof(T).GetProperty(childProperties[0]);
                var paramExp = Expression.Parameter(typeof(T), "SomeObject");
                propExpression = Expression.MakeMemberAccess(paramExp, property);
                for (int i = 1; i < childProperties.Length; i++)
                {
                    property = property.PropertyType.GetProperty(childProperties[i]);
                    propExpression = Expression.MakeMemberAccess(propExpression, property);
                }
                propType = propExpression.Type;
                propExpression = Expression.Block(new[] { paramExp }, new[]{ propExpression });

            }
            else
            {
                propExpression = MemberExpression.Property(param, r.MemberName);
                propType = propExpression.Type;
            }

            // is the operator a known .NET operator?
            if (ExpressionType.TryParse(r.Operator, out tBinary))
            {
                var right = Expression.Constant(Convert.ChangeType(r.TargetValue, propType));
                // use a binary operation, e.g. 'Equal' -> 'u.Age == 15'
                return Expression.MakeBinary(tBinary, propExpression, right);
            }
            else
            {
                var method = propType.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(propExpression, method, right);
            }
        }

    }
    public class Rule
    {
        public string MemberName { get; set; }
        public string Operator { get; set; }
        public string TargetValue { get; set; }
    }
}

Et c'est le test qui échoue

[TestMethod]
public void ChildPropertyRuleTest()
{
    Container container = new Container()
    {
        Repository = "TestRepo",
        Shipment = new Shipment() { OrderNumber = "555" }
    };

    MicroRuleEngine mr = new MicroRuleEngine();
    var rules = new List<Rule>() { new Rule() { MemberName = "Shipment.OrderNumber", Operator = "Contains", TargetValue = "55" } };
    var pases = mr.PassesRules<Container>(rules, container);
    Assert.IsTrue(!pases);
}

Réponse acceptée

Donc, l'erreur que je rencontrais était que tous les exemples que j'avais lus en essayant de savoir comment accéder aux sous-propriétés utilisaient les expressions MemberAccess pour décrire les propriétés et j'ai constaté que l'utilisation de PropertyExpressions fonctionnait sans problème pour les tests simples que j'ai. Ci-dessous une mise à jour qui fonctionne maintenant

public class MicroRuleEngine
    {
        public bool PassesRules<T>(List<Rule> rules, T toInspect)
        {
            return this.CompileRules<T>(rules).Invoke(toInspect);
        }
        public Func<T, bool> CompileRule<T>(Rule r)
        {
            var paramUser = Expression.Parameter(typeof(T));
            Expression expr = BuildExpr<T>(r, paramUser);

            return Expression.Lambda<Func<T, bool>>(expr, paramUser).Compile();
        }

        public Func<T, bool> CompileRules<T>(IList<Rule> rules)
        {
            var paramUser = Expression.Parameter(typeof(T));
            List<Expression> expressions = new List<Expression>();
            foreach (var r in rules)
            {
                expressions.Add(BuildExpr<T>(r, paramUser));
            }
            var expr = AndExpressions(expressions);

            return Expression.Lambda<Func<T, bool>>(expr, paramUser).Compile();
        }

        Expression AndExpressions(IList<Expression> expressions)
        {
            if(expressions.Count == 1)
                return expressions[0];
            Expression exp = Expression.And(expressions[0], expressions[1]);
            for(int i = 2; expressions.Count > i; i++)
            {
                exp = Expression.And(exp, expressions[i]);
            }
            return exp;
        }

        Expression BuildExpr<T>(Rule r, ParameterExpression param)
        {
            Expression propExpression;
            Type propType;
            ExpressionType tBinary;
            if (r.MemberName.Contains('.'))
            {
                String[] childProperties = r.MemberName.Split('.');
                var property = typeof(T).GetProperty(childProperties[0]);
                var paramExp = Expression.Parameter(typeof(T), "SomeObject");

                propExpression = Expression.PropertyOrField(param, childProperties[0]);
                for (int i = 1; i < childProperties.Length; i++)
                {
                    property = property.PropertyType.GetProperty(childProperties[i]);
                    propExpression = Expression.PropertyOrField(propExpression, childProperties[i]);
                }
                propType = propExpression.Type;
            }
            else
            {
                propExpression = Expression.PropertyOrField(param, r.MemberName);
                propType = propExpression.Type;
            }

            // is the operator a known .NET operator?
            if (ExpressionType.TryParse(r.Operator, out tBinary))
            {
                var right = Expression.Constant(Convert.ChangeType(r.TargetValue, propType));
                // use a binary operation, e.g. 'Equal' -> 'u.Age == 15'
                return Expression.MakeBinary(tBinary, propExpression, right);
            }
            else
            {
                var method = propType.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(propExpression, method, right);
            }
        }

    }
    public class Rule
    {
        public string MemberName { get; set; }
        public string Operator { get; set; }
        public string TargetValue { get; set; }
    }

Réponse populaire

Ne croyez pas que vous ayez jeté un coup d'œil à l'analyseur d'expression dynamique fourni avec le projet exemple pour les exemples VS2008 Il comprend un type appelé ExpressionParser qui peut être utilisé pour convertir des expressions de chaîne en instances Expression . J'ai déjà utilisé cela pour transformer des expressions de chaîne en délégués compilables, par exemple, je pourrais faire quelque chose comme:

string expression = "(1 + 2)";
var func = FunctionFactory.Create<int>(expression);

int result = func(1, 2); // Result should be 3.

... où FunctionFactory est une enveloppe autour du type ExpressionParser . Je pourrais aussi faire:

expression = "(a * b)";
var func2 = FunctionFactory.Create<int, int, int>(expresion new[] { "a", "b" });

int result = func2(10, 50); // Result should be 500;

Ou quelque chose d'un peu tangible:

expression = "(Age == 5)";
var func3 = FunctionFactory.Create<Person, bool>(expression);

bool isFive = func3(new Person { Age = 5 });

Cela vous serait-il utile? Vous pouvez lire mon article de blog ici .



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow