Сборка MicroRuleEngine с использованием LinqExpressions - PullRequest
1 голос
/ 09 сентября 2011

Итак, я создаю MicroRuleEngine (хотелось бы, чтобы это выглядело как проект OpenSource), и я сталкиваюсь с нулевой ссылкой Ошибка при выполнении скомпилированного ExpressionTree, и я не совсем уверен, почему. Правила против простых свойств работают, но не работают с дочерними свойствами, такими как Customer.Client.Address.StreetName и т. Д., Не работают.

Ниже приведен 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; }
    }
}

И это тест, который не проходит

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

Ответы [ 3 ]

1 голос
/ 09 сентября 2011

Итак, ошибка, с которой я столкнулся, заключалась во всех примерах, которые я прочитал, пытаясь выяснить, как получить доступ к под-свойствам, используя выражения MemberAccess для перехода по свойствам, и я обнаружил, что использование PropertyExpressions без проблем работает для простых тестов, которые яиметь.Ниже приведено обновление, которое сейчас работает

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; }
    }
1 голос
/ 09 сентября 2011

Не предполагалось, что вы просматривали анализатор динамических выражений, который был включен в качестве примера проекта для VS2008 образцов .Он включает тип, называемый ExpressionParser, который можно использовать для преобразования строковых выражений в Expression экземпляры.Я использовал это ранее для преобразования строковых выражений в скомпилируемые делегаты, например, я мог сделать что-то вроде:

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

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

... где FunctionFactory - это обертка вокруг типа ExpressionParser.Я также мог бы сделать:

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

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

Или что-то немного осязаемое:

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

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

Это вам пригодится?Вы можете прочитать мою статью в блоге здесь .

0 голосов
/ 09 сентября 2011

Возможно ли в вашем тесте, что свойство Shipment не было инициализировано в вашем контейнере?

Пара других рекомендаций: если вы должны использовать выражения, рассмотрите возможность кэширования скомпилированной версии выражения, чтобы оно моглоиспользовать повторно, а не перекомпилировать его каждый раз, когда вы захотите его использовать.

Во-вторых, есть ли конкретная причина, по которой вы решили использовать выражения, а не просто использовать Func в своем правиле?Обычно при создании таких механизмов правил мой класс правил определяется следующим образом:

public class Rule
{
   public string Description {get; set;}
   public Func<T, bool> RuleToApply {get; set;}
}

Учитывая это, я создаю свою коллекцию правил следующим образом:

 var rules = new List<Rule>() { 
     new Rule { Description = "OrderNumber Contains 55", 
                RuleToApply = order => order.OrderNumber.Contains("55") } 
 }; 

И PassesRule становится:

public bool PassesRules<T>(List<Rule> rules, T toInspect) 
{ 
    return rules.All(rule => rule(toInspect));
} 

Другое преимущество в том, что вместо передачи строк и выражения выражения я сохраняю поддержку безопасности типов и рефакторинга, которую я терял бы, используя строки и динамически создаваемые выражения.

ДругойОб этом следует помнить, если вы создаете парсер выражений многократного использования, убедитесь, что вы настраиваете тесты в VB, а также в C #, потому что они не всегда генерируют одно и то же дерево выражений под обложками.В частности, добавьте VB-тесты на равенство строк (city = "London").Я видел бесчисленных провайдеров LINQ, которые пропустили этот простой случай.

...