使用 LinqExpressions 构建 MicroRuleEngine
因此,我正在构建一个 MicroRuleEngine(希望看到它作为一个开源项目起飞),并且在执行编译的 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);
}
So I am building a MicroRuleEngine (Would love to see this take off as an OpenSource project) and I am running into a null reference Error When executing the compiled ExpressionTree and I am not exactly sure why. Rules against the simple properties work but going against Child Properties aka Customer.Client.Address.StreetName etc. do not work.
Below is the 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);
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
不认为您已经看过动态表达式解析器,它作为 VS2008 的示例项目捆绑在一起样本。它包括一个名为
ExpressionParser
的类型,可用于将字符串表达式转换为Expression
实例。我之前使用过它来将字符串表达式转换为可编译委托,例如,我可以执行以下操作:...其中
FunctionFactory
是ExpressionParser
类型的包装器。我还可以做:
或者一些有形的事情:
这对你有用吗?您可以在此处阅读我的博客文章。
Don't supposed you've had a look at the dynamic expression parser which was bundled as an example project for the VS2008 samples. It includes a type called an
ExpressionParser
which can be used to convert string expressions toExpression
instances. I've used this previously to turn string expressions into compilable delegates, e.g, I could do something like:...where
FunctionFactory
is a wrapper around theExpressionParser
type.I could also do:
Or something a little tangible:
Would this be of any use to you? You can read my blog article here.
因此,我遇到的错误是我在试图找出如何访问子属性时阅读的所有示例都使用 MemberAccess Expressions 来遍历属性,并且我发现使用 PropertyExpressions 对于我的简单测试来说没有问题。以下是目前正在运行的更新
So the error I was running into was all the examples I read in trying to find out how to access sub properties were using MemberAccess Expressions to walk down the properties and I found that using PropertyExpressions worked without a problem for the simple tests I have. Below is an update that is now working
在您的测试中,是否有可能您的容器中的 Shipment 属性尚未初始化?
其他一些建议:如果必须使用表达式,请考虑缓存表达式的编译版本,以便可以重用它,而不必在每次要使用它时重新编译它。
其次,您选择在规则中使用表达式而不是仅使用 Func 是否有特定原因?通常,在创建这样的规则引擎时,我的规则类定义为:
鉴于此,我按如下方式实例化我的规则集合:
并且 PassesRule 变为:
这里的另一个优点是,我不传递字符串和评估表达式,而是保留类型通过使用字符串和动态构建表达式,我将失去安全和重构支持。
如果您正在构建可重用的表达式解析器,请记住另一件事,请确保在 VB 和 C# 中设置测试,因为它们并不总是在幕后生成相同的表达式树。特别是,添加字符串相等性的 VB 测试(city = "London")。我见过无数的 LINQ 提供商都忽略了这个简单的情况。
Is it possible in your test that the Shipment property has not been initialized in your container?
A couple other recommendations: If you must use expressions, consider caching the compiled version of the expression so that it can be reused rather than having to recompile it each time you want to use it.
Second, is there a specific reason you elected to go with expressions rather than just using Func in your rule? Typically when creating rules engines like this, my rule class is defined as somthing like:
Given that, I instantiate my Rules collection as follows:
And PassesRule becomes:
The other advantage here is that instead of passing strings and evaulating expressions, I keep the type safety and refactoring support that I would loose by using strings and dynamically building expressions.
Another thing to keep in mind if you are building a reusable expression parser, make sure to set up tests in VB as well as C# because they don't always generate the same expression tree under the covers. In particular, add VB tests for string equality (city = "London"). I've seen countless LINQ providers which have overlooked this simple case.