使用LinqExpressions构建microroruleengine
本文关键字:microroruleengine 构建 LinqExpressions 使用 | 更新日期: 2023-09-27 18:08:17
所以我正在构建一个microroruleengine(很想看到这个作为一个开源项目起飞),我在执行编译的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; }
}
}
And This is the Test that is Failing
[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);
}
不要以为您已经看过动态表达式解析器,它是作为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 });
这对你有用吗?你可以在这里阅读我的博客文章。
所以我遇到的错误是我在试图找出如何访问子属性时读到的所有例子都是使用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; }
}
是否有可能在您的测试中,装运属性尚未在您的容器中初始化?
其他一些建议:如果必须使用表达式,请考虑缓存表达式的编译版本,以便可以重用它,而不必每次使用它时都重新编译它。
第二,你选择使用表达式而不是在你的规则中使用Func有什么特殊的原因吗?通常在创建这样的规则引擎时,我的规则类定义为:
public class Rule
{
public string Description {get; set;}
public Func<T, bool> RuleToApply {get; set;}
}
给定这个,我实例化我的Rules集合如下:
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#中设置测试,因为它们并不总是在底层生成相同的表达式树。特别地,添加字符串相等(city = "London")的VB测试。我看到过无数LINQ提供程序忽略了这个简单的例子。