From 12a25b64d1c53321aad146a6388908ce982e78e7 Mon Sep 17 00:00:00 2001 From: Hamidreza Mohaqeq Date: Mon, 13 Feb 2017 10:47:39 +0100 Subject: [PATCH 1/3] Add setting result value in the rule option to compiler. --- MicroRuleEngine/ExpressionBuilder.cs | 24 ++++++++++++++----- MicroRuleEngine/MicroRuleEngine.csproj | 2 -- MicroRuleEngine/Rule.cs | 32 ++++++++++++++++++++++++++ MicroRuleEngine/RuleCompiler.cs | 9 ++++---- MicroRuleEngine/RuleValue.cs | 10 -------- MicroRuleEngine/RuleValueString.cs | 6 ----- 6 files changed, 54 insertions(+), 29 deletions(-) delete mode 100644 MicroRuleEngine/RuleValue.cs delete mode 100644 MicroRuleEngine/RuleValueString.cs diff --git a/MicroRuleEngine/ExpressionBuilder.cs b/MicroRuleEngine/ExpressionBuilder.cs index c2da813..8cd072f 100644 --- a/MicroRuleEngine/ExpressionBuilder.cs +++ b/MicroRuleEngine/ExpressionBuilder.cs @@ -24,21 +24,24 @@ internal static class ExpressionBuilder ExpressionType.OrElse }; - public static Expression Build(Rule rule, ParameterExpression parameterExpression) + public static Expression Build(Rule rule, ParameterExpression parameterExpression, bool withValue) { ExpressionType nestedOperator; return Enum.TryParse(rule.Operator, out nestedOperator) && NestedOperators.Contains(nestedOperator) && rule.Rules != null && rule.Rules.Any() - ? Build(rule.Rules, parameterExpression, nestedOperator) - : BuildExpression(rule, parameterExpression); + ? withValue + ? BuildSetValue(rule, Build(rule.Rules, parameterExpression, nestedOperator, withValue)) + : Build(rule.Rules, parameterExpression, nestedOperator, withValue) + : withValue + ? BuildSetValue(rule, BuildExpression(rule, parameterExpression)) + : BuildExpression(rule, parameterExpression); } - public static Expression Build(IEnumerable rules, ParameterExpression parameterExpression, ExpressionType operation) + public static Expression Build(IEnumerable rules, ParameterExpression parameterExpression, ExpressionType operation, bool withValue) { - var expressions = rules.Select(r => Build(r, parameterExpression)); - + var expressions = rules.Select(r => Build(r, parameterExpression, withValue)); return Build(expressions, operation); } @@ -158,5 +161,14 @@ private static Expression StringToExpression(string value, Type propType) ? Enum.Parse(propType, value) : Convert.ChangeType(value, propType)); } + + private static Expression BuildSetValue(Rule rule, Expression exp) + { + return rule != null + ? Expression.Convert(Expression.Assign( + Expression.Property(Expression.Constant(rule), "Result"), + Expression.Convert(exp, typeof(bool?))), typeof(bool)) + : exp; + } } } diff --git a/MicroRuleEngine/MicroRuleEngine.csproj b/MicroRuleEngine/MicroRuleEngine.csproj index 2f09410..53a86ee 100644 --- a/MicroRuleEngine/MicroRuleEngine.csproj +++ b/MicroRuleEngine/MicroRuleEngine.csproj @@ -45,8 +45,6 @@ - - {rule.Result.ToString() ?? "Not Evaluted"}" + + (string.IsNullOrWhiteSpace(rule.Message) ? "\r\n" : $" : {rule.Message}\r\n"); + if (rule.Rules != null) + foreach (var r in rule.Rules) + result = RuleResults(r, result, level + 1); + return result; + } + + private static void RuleClear(Rule rule) + { + if (rule.Rules != null) + foreach (var r in rule.Rules) + RuleClear(r); + rule.Result = null; + } } } \ No newline at end of file diff --git a/MicroRuleEngine/RuleCompiler.cs b/MicroRuleEngine/RuleCompiler.cs index 4704623..089e6a4 100644 --- a/MicroRuleEngine/RuleCompiler.cs +++ b/MicroRuleEngine/RuleCompiler.cs @@ -6,18 +6,17 @@ namespace MicroRuleEngine { public static class RuleCompiler { - public static Func Compile(Rule rule) + public static Func Compile(Rule rule, bool withValue = false) { var expressionParameter = Expression.Parameter(typeof(T)); - var expression = ExpressionBuilder.Build(rule, expressionParameter); - + var expression = ExpressionBuilder.Build(rule, expressionParameter, withValue); return Expression.Lambda>(expression, expressionParameter).Compile(); } - public static Func Compile(IEnumerable rules) + public static Func Compile(IEnumerable rules, bool withValue = false) { var expressionParameter = Expression.Parameter(typeof(T)); - var expression = ExpressionBuilder.Build(rules, expressionParameter, ExpressionType.And); + var expression = ExpressionBuilder.Build(rules, expressionParameter, ExpressionType.And, withValue); return Expression.Lambda>(expression, expressionParameter).Compile(); } } diff --git a/MicroRuleEngine/RuleValue.cs b/MicroRuleEngine/RuleValue.cs deleted file mode 100644 index 01fc001..0000000 --- a/MicroRuleEngine/RuleValue.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; - -namespace MicroRuleEngine -{ - public class RuleValue - { - public T Value { get; set; } - public IEnumerable Rules { get; set; } - } -} \ No newline at end of file diff --git a/MicroRuleEngine/RuleValueString.cs b/MicroRuleEngine/RuleValueString.cs deleted file mode 100644 index 410152e..0000000 --- a/MicroRuleEngine/RuleValueString.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace MicroRuleEngine -{ - public class RuleValueString : RuleValue - { - } -} \ No newline at end of file From 094810b3c935a77ab1bfc3b47211e7316c36d0af Mon Sep 17 00:00:00 2001 From: Hamidreza Mohaqeq Date: Wed, 22 Feb 2017 14:35:17 +0100 Subject: [PATCH 2/3] Add Collection Methods to ExpressionBuilder --- MicroRuleEngine.Tests/ExampleUsage.cs | 192 ++++++++++++++++------- MicroRuleEngine.Tests/Models/Order.cs | 1 + MicroRuleEngine/ExpressionBuilder.cs | 215 +++++++++++++++++++------- MicroRuleEngine/RuleCompiler.cs | 4 +- 4 files changed, 290 insertions(+), 122 deletions(-) diff --git a/MicroRuleEngine.Tests/ExampleUsage.cs b/MicroRuleEngine.Tests/ExampleUsage.cs index 7b43a03..0ad52ab 100644 --- a/MicroRuleEngine.Tests/ExampleUsage.cs +++ b/MicroRuleEngine.Tests/ExampleUsage.cs @@ -32,11 +32,11 @@ public void ChildProperties() { Order order = GetOrder(); Rule rule = new Rule - { - MemberName = "Customer.Country.CountryCode", - Operator = ExpressionType.Equal.ToString("g"), - TargetValue = "AUS" - }; + { + MemberName = "Customer.Country.CountryCode", + Operator = ExpressionType.Equal.ToString("g"), + TargetValue = "AUS" + }; var compiledRule = MRE.Instance.Compile(rule); bool passes = compiledRule(order); Assert.IsTrue(passes); @@ -51,38 +51,37 @@ public void ConditionalLogic() { Order order = GetOrder(); Rule rule = new Rule + { + Operator = ExpressionType.AndAlso.ToString("g"), + Rules = new List + { + new Rule + { + MemberName = "Customer.LastName", + TargetValue = "Doe", + Operator = "Equal" + }, + new Rule + { + Operator = "Or", + Rules = new List + { + new Rule { - Operator = ExpressionType.AndAlso.ToString("g"), - Rules = - new List - { - new Rule - { - MemberName = "Customer.LastName", - TargetValue = "Doe", - Operator = "Equal" - }, - new Rule - { - Operator = "Or", - Rules = new List - { - new Rule - { - MemberName = "Customer.FirstName", - TargetValue = "John", - Operator = "Equal" - }, - new Rule - { - MemberName = "Customer.FirstName", - TargetValue = "Jane", - Operator = "Equal" - } - } - } - } - }; + MemberName = "Customer.FirstName", + TargetValue = "John", + Operator = "Equal" + }, + new Rule + { + MemberName = "Customer.FirstName", + TargetValue = "Jane", + Operator = "Equal" + } + } + } + } + }; var fakeName = MRE.Instance.Compile(rule); bool passes = fakeName(order); Assert.IsTrue(passes); @@ -97,10 +96,10 @@ public void BooleanMethods() { Order order = GetOrder(); Rule rule = new Rule - { - Operator = "HasItem", //The Order Object Contains a method named 'HasItem' that returns true/false - Inputs = new List { "Test" } - }; + { + Operator = "HasItem", //The Order Object Contains a method named 'HasItem' that returns true/false + Inputs = new List { "Test" } + }; var boolMethod = MRE.Instance.Compile(rule); bool passes = boolMethod(order); Assert.IsTrue(passes); @@ -116,11 +115,11 @@ public void ChildPropertyBooleanMethods() { Order order = GetOrder(); Rule rule = new Rule - { - MemberName = "Customer.FirstName", - Operator = "EndsWith", //Regular method that exists on string.. As a note expression methods are not available - Inputs = new List { "ohn" } - }; + { + MemberName = "Customer.FirstName", + Operator = "EndsWith", //Regular method that exists on string.. As a note expression methods are not available + Inputs = new List { "ohn" } + }; var childPropCheck = MRE.Instance.Compile(rule); bool passes = childPropCheck(order); Assert.IsTrue(passes); @@ -146,15 +145,16 @@ public void ChildPropertyOfNullBooleanMethods() Assert.IsFalse(passes); } + [TestMethod] public void RegexIsMatch() //Had to add a Regex evaluator to make it feel 'Complete' { Order order = GetOrder(); Rule rule = new Rule - { - MemberName = "Customer.FirstName", - Operator = "IsMatch", - TargetValue = @"^[a-zA-Z0-9]*$" - }; + { + MemberName = "Customer.FirstName", + Operator = "IsMatch", + TargetValue = @"^[a-zA-Z0-9]*$" + }; var regexCheck = MRE.Instance.Compile(rule); bool passes = regexCheck(order); Assert.IsTrue(passes); @@ -164,19 +164,91 @@ public void RegexIsMatch() //Had to add a Regex evaluator to make it feel 'Compl Assert.IsFalse(passes); } + [TestMethod] + public void CollectionItemMethods() + { + Order order = this.GetOrder(); + Rule rule = new Rule() + { + Operator = "Any", + MemberName = "Items", + Rules = new List { + new Rule + { + Operator = "Equal", + MemberName ="ItemCode", + TargetValue="Test" + }, + new Rule + { + Operator ="GreaterThan", + MemberName = "Cost", + TargetValue = "3.00" + } + } + }; + var boolMethod = RuleCompiler.Compile(rule, true); + bool passes = boolMethod(order); + Assert.IsTrue(passes); + + var item = order.Items.First(x => x.ItemCode == "Test"); + item.ItemCode = "Changed"; + + passes = boolMethod(order); + Assert.IsFalse(passes); + } + + [TestMethod] + public void CollectionAggregateMethods() + { + Order order = this.GetOrder(); + Rule rule = new Rule() + { + Operator = "Equal", + MemberName = "Prices.Sum", + TargetValue = "51.5" + }; + var boolMethod = RuleCompiler.Compile(rule, true); + bool passes = boolMethod(order); + Assert.IsTrue(passes); + } + + [TestMethod] + public void CollectionGenericMethods() + { + Order order = this.GetOrder(); + Rule rule = new Rule() + { + Operator = "Equal", + MemberName = "Prices.First", + TargetValue = "10.2" + }; + var boolMethod = RuleCompiler.Compile(rule, true); + bool passes = boolMethod(order); + Assert.IsTrue(passes); + } + public Order GetOrder() { return new Order - { - OrderId = 1, - Customer = Customer.Make("John", "Doe", "AUS"), - Items = new List - { - Item.Make("MM23", 5.25M), - Item.Make("LD45", 5.25M), - Item.Make("Test", 3.33M), - } - }; + { + OrderId = 1, + Customer = Customer.Make("John", "Doe", "AUS"), + Items = new List + { + Item.Make("MM23", 5.25M), + Item.Make("LD45", 5.25M), + Item.Make("Test", 3.33M), + }, + Prices = new decimal[] + { + 10.2M, + 5, + 6.3M, + 8, + 22 + }, + }; } } } diff --git a/MicroRuleEngine.Tests/Models/Order.cs b/MicroRuleEngine.Tests/Models/Order.cs index 4943049..2a3872d 100644 --- a/MicroRuleEngine.Tests/Models/Order.cs +++ b/MicroRuleEngine.Tests/Models/Order.cs @@ -14,6 +14,7 @@ public Order() public int OrderId { get; set; } public Customer Customer { get; set; } public List Items { get; set; } + public IEnumerable Prices { get; set; } public bool HasItem(string itemCode) { diff --git a/MicroRuleEngine/ExpressionBuilder.cs b/MicroRuleEngine/ExpressionBuilder.cs index 8cd072f..3e7d60e 100644 --- a/MicroRuleEngine/ExpressionBuilder.cs +++ b/MicroRuleEngine/ExpressionBuilder.cs @@ -10,43 +10,47 @@ internal static class ExpressionBuilder { private const string StrIsMatch = "IsMatch"; private const string StrNull = "null"; - private static Type typeOfNullReferenceException = typeof(NullReferenceException); - private static Type typeOfBool = typeof(bool); - private static Type typeOfRegex = typeof(Regex); - private static Type typeOfString = typeof(string); - private static Type typeOfRegexOptions = typeof(RegexOptions); + private static readonly string[] StrEnumerableAggregateMethodes = new[] { "Average", "Max", "Min", "Sum" }; + private static readonly string[] StrEnumerableGenericMethodes = new[] { "Count", "LongCount", "First", "FirstOrDefault" + , "Last", "LastOrDefault", "Single", "SingleOrDefault" }; + private static readonly string[] StrEnumerableItemMethodes = new[] { "All", "Any" }; + private static readonly Type typeOfNullReferenceException = typeof(NullReferenceException); + private static readonly Type typeOfBool = typeof(bool); + private static readonly Type typeOfRegex = typeof(Regex); + private static readonly Type typeOfString = typeof(string); + private static readonly Type typeOfRegexOptions = typeof(RegexOptions); private static readonly ExpressionType[] NestedOperators = { ExpressionType.And, ExpressionType.AndAlso, ExpressionType.Or, - ExpressionType.OrElse + ExpressionType.OrElse, }; - public static Expression Build(Rule rule, ParameterExpression parameterExpression, bool withValue) + public static Expression Build(Type type, Rule rule, ParameterExpression parameterExpression, bool withValue) { ExpressionType nestedOperator; - return Enum.TryParse(rule.Operator, out nestedOperator) && - NestedOperators.Contains(nestedOperator) && - rule.Rules != null && - rule.Rules.Any() + var isComposite = Enum.TryParse(rule.Operator, out nestedOperator) + && NestedOperators.Contains(nestedOperator) + && rule.Rules != null + && rule.Rules.Any(); + return isComposite ? withValue - ? BuildSetValue(rule, Build(rule.Rules, parameterExpression, nestedOperator, withValue)) - : Build(rule.Rules, parameterExpression, nestedOperator, withValue) + ? BuildRuleSetValueExpression(rule, Build(type, rule.Rules, parameterExpression, nestedOperator, withValue)) + : Build(type, rule.Rules, parameterExpression, nestedOperator, withValue) : withValue - ? BuildSetValue(rule, BuildExpression(rule, parameterExpression)) - : BuildExpression(rule, parameterExpression); + ? BuildRuleSetValueExpression(rule, BuildExpression(type, rule, parameterExpression)) + : BuildExpression(type, rule, parameterExpression); } - public static Expression Build(IEnumerable rules, ParameterExpression parameterExpression, ExpressionType operation, bool withValue) + public static Expression Build(Type type, IEnumerable rules, ParameterExpression parameterExpression, ExpressionType operation, bool withValue) { - var expressions = rules.Select(r => Build(r, parameterExpression, withValue)); + var expressions = rules.Select(r => Build(type, r, parameterExpression, withValue)); return Build(expressions, operation); } - private static Expression Build(IEnumerable expressions, ExpressionType operationType) { Func expressionAggregateMethod; @@ -65,7 +69,6 @@ private static Expression Build(IEnumerable expressions, ExpressionT expressionAggregateMethod = Expression.And; break; } - return BuildExpression(expressions, expressionAggregateMethod); } @@ -79,44 +82,16 @@ private static Expression BuildExpression(IEnumerable expressions, F } - private static Expression BuildExpression(Rule rule, Expression expression) + private static Expression BuildExpression(Type type, Rule rule, Expression expression) { - Expression propExpression; - Type propType; - if (string.IsNullOrEmpty(rule.MemberName)) //check is against the object itself - { - propExpression = expression; - propType = propExpression.Type; - } - else if (rule.MemberName.Contains('.')) //Child property - { - var childProperties = rule.MemberName.Split('.'); - var property = typeof(T).GetProperty(childProperties[0]); - // not being used? - // ParameterExpression paramExp = Expression.Parameter(typeof(T), "SomeObject"); - - propExpression = Expression.PropertyOrField(expression, childProperties[0]); - for (var i = 1; i < childProperties.Length; i++) - { - // not being used? - // PropertyInfo orig = property; - if (property == null) continue; - property = property.PropertyType.GetProperty(childProperties[i]); - if (property == null) continue; - propExpression = Expression.PropertyOrField(propExpression, childProperties[i]); - } - propType = propExpression.Type; - } - else //Property - { - propExpression = Expression.PropertyOrField(expression, rule.MemberName); - propType = propExpression.Type; - } + Expression propExpression = PropertyExpression(type, rule, expression); + Type propType = propExpression.Type; propExpression = Expression.TryCatch( Expression.Block(propExpression.Type, propExpression), Expression.Catch(typeOfNullReferenceException, Expression.Default(propExpression.Type)) ); + ExpressionType tBinary; // is the operator a known .NET operator? if (Enum.TryParse(rule.Operator, out tBinary)) @@ -124,6 +99,7 @@ private static Expression BuildExpression(Rule rule, Expression expression) var right = StringToExpression(rule.TargetValue, propType); return Expression.MakeBinary(tBinary, propExpression, right); } + // is the operator a RegEx IsMatch operator? if (rule.Operator == StrIsMatch) { return Expression.Call( @@ -138,13 +114,27 @@ private static Expression BuildExpression(Rule rule, Expression expression) propExpression, Expression.Constant(rule.TargetValue, typeOfString), Expression.Constant(RegexOptions.IgnoreCase, typeOfRegexOptions) - ); + ); } - //Invoke a method on the Property + // is the operator a collection item operator? + if (StrEnumerableItemMethodes.Any(m => m == rule.Operator)) + { + var elementType = ElementType(propType); + var lambdaParam = Expression.Parameter(elementType, "lambdaParam"); + return Expression.Call( + typeof(Enumerable).GetMethods().First(m => + m.Name == rule.Operator + && m.GetParameters().Count() == 2 + ).MakeGenericMethod(elementType), + propExpression, + Expression.Lambda(Build(elementType, rule.Rules, lambdaParam, ExpressionType.AndAlso, true), lambdaParam) + ); + } + // Invoke a method on the Property var inputs = rule.Inputs.Select(x => x.GetType()).ToArray(); var methodInfo = propType.GetMethod(rule.Operator, inputs); if (!methodInfo.IsGenericMethod) - inputs = null; //Only pass in type information to a Generic Method + inputs = null; // Only pass in type information to a Generic Method var expressions = rule.Inputs.Select(Expression.Constant).ToArray(); return Expression.TryCatch( @@ -153,6 +143,80 @@ private static Expression BuildExpression(Rule rule, Expression expression) ); } + private static Expression BuildRuleSetValueExpression(Rule rule, Expression exp) + { + return rule != null + ? Expression.Convert(Expression.Assign( + Expression.Property(Expression.Constant(rule), "Result"), + Expression.Convert(exp, typeof(bool?))), typeof(bool)) + : exp; + } + + private static Expression PropertyExpression(Type type, Rule rule, Expression expression) + { + // Object itself + if (string.IsNullOrEmpty(rule.MemberName)) + { + return expression; + } + //Child property + else if (rule.MemberName.Contains('.')) + { + var childProperties = rule.MemberName.Split('.'); + var property = type.GetProperty(childProperties[0]); + + Expression propExpression = Expression.PropertyOrField(expression, childProperties[0]); + for (var i = 1; i < childProperties.Length; i++) + { + if (property == null) continue; + property = property.PropertyType.GetProperty(childProperties[i]); + if (property == null) + { + propExpression = EnumarableMethodToExpression(propExpression, childProperties[i]); + continue; + } + propExpression = Expression.PropertyOrField(propExpression, childProperties[i]); + } + return propExpression; + } + // Property + else + { + if (type.GetProperty(rule.MemberName) == null) + return EnumarableMethodToExpression(expression, rule.MemberName); + + return Expression.PropertyOrField(expression, rule.MemberName); + } + } + + private static Expression EnumarableMethodToExpression(Expression propExpression, string methodName) + { + var elementType = ElementType(propExpression.Type); + if (propExpression.Type.GetInterface("IEnumerable") != null) + // Check if it is a collection aggregate method + if (StrEnumerableAggregateMethodes.Any(m => m == methodName)) + { + propExpression = Expression.Call( + typeof(Enumerable), + methodName, + null, + propExpression + ); + } + // Check if it is a collection generic method + else if (StrEnumerableGenericMethodes.Any(m => m == methodName)) + { + propExpression = Expression.Call( + typeof(Enumerable), + methodName, + new[] { elementType }, + propExpression + ); + } + + return propExpression; + } + private static Expression StringToExpression(string value, Type propType) { return value.ToLower() == StrNull @@ -162,13 +226,44 @@ private static Expression StringToExpression(string value, Type propType) : Convert.ChangeType(value, propType)); } - private static Expression BuildSetValue(Rule rule, Expression exp) + private static Type ElementType(Type seqType) { - return rule != null - ? Expression.Convert(Expression.Assign( - Expression.Property(Expression.Constant(rule), "Result"), - Expression.Convert(exp, typeof(bool?))), typeof(bool)) - : exp; + Type ienum = FindIEnumerable(seqType); + if (ienum == null) return seqType; + return ienum.GetGenericArguments()[0]; + } + + private static Type FindIEnumerable(Type seqType) + { + if (seqType == null || seqType == typeof(string)) + return null; + if (seqType.IsArray) + return typeof(IEnumerable<>).MakeGenericType(seqType.GetElementType()); + if (seqType.IsGenericType) + { + foreach (Type arg in seqType.GetGenericArguments()) + { + Type ienum = typeof(IEnumerable<>).MakeGenericType(arg); + if (ienum.IsAssignableFrom(seqType)) + { + return ienum; + } + } + } + Type[] ifaces = seqType.GetInterfaces(); + if (ifaces != null && ifaces.Length > 0) + { + foreach (Type iface in ifaces) + { + Type ienum = FindIEnumerable(iface); + if (ienum != null) return ienum; + } + } + if (seqType.BaseType != null && seqType.BaseType != typeof(object)) + { + return FindIEnumerable(seqType.BaseType); + } + return null; } } } diff --git a/MicroRuleEngine/RuleCompiler.cs b/MicroRuleEngine/RuleCompiler.cs index 089e6a4..8ef63db 100644 --- a/MicroRuleEngine/RuleCompiler.cs +++ b/MicroRuleEngine/RuleCompiler.cs @@ -9,14 +9,14 @@ public static class RuleCompiler public static Func Compile(Rule rule, bool withValue = false) { var expressionParameter = Expression.Parameter(typeof(T)); - var expression = ExpressionBuilder.Build(rule, expressionParameter, withValue); + var expression = ExpressionBuilder.Build(typeof(T), rule, expressionParameter, withValue); return Expression.Lambda>(expression, expressionParameter).Compile(); } public static Func Compile(IEnumerable rules, bool withValue = false) { var expressionParameter = Expression.Parameter(typeof(T)); - var expression = ExpressionBuilder.Build(rules, expressionParameter, ExpressionType.And, withValue); + var expression = ExpressionBuilder.Build(typeof(T), rules, expressionParameter, ExpressionType.And, withValue); return Expression.Lambda>(expression, expressionParameter).Compile(); } } From 7e4d7690f346ea2140c9eccaa7f7b5d911971fd4 Mon Sep 17 00:00:00 2001 From: Hamidreza Mohaqeq Date: Thu, 2 Mar 2017 09:39:30 +0100 Subject: [PATCH 3/3] Add support for any methods call without lambda. Signed-off-by: Hamidreza Mohaqeq --- MicroRuleEngine.Tests/ExampleUsage.cs | 18 ++++++++++++++++++ MicroRuleEngine/ExpressionBuilder.cs | 24 ++++++++++++++++-------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/MicroRuleEngine.Tests/ExampleUsage.cs b/MicroRuleEngine.Tests/ExampleUsage.cs index 0ad52ab..64b1590 100644 --- a/MicroRuleEngine.Tests/ExampleUsage.cs +++ b/MicroRuleEngine.Tests/ExampleUsage.cs @@ -198,6 +198,24 @@ public void CollectionItemMethods() Assert.IsFalse(passes); } + [TestMethod] + public void CollectionItemExists() + { + Order order = this.GetOrder(); + Rule rule = new Rule() + { + Operator = "Any", + MemberName = "Items", + }; + var boolMethod = RuleCompiler.Compile(rule, true); + bool passes = boolMethod(order); + Assert.IsTrue(passes); + + order.Items.Clear(); + passes = boolMethod(order); + Assert.IsFalse(passes); + } + [TestMethod] public void CollectionAggregateMethods() { diff --git a/MicroRuleEngine/ExpressionBuilder.cs b/MicroRuleEngine/ExpressionBuilder.cs index 3e7d60e..74f7093 100644 --- a/MicroRuleEngine/ExpressionBuilder.cs +++ b/MicroRuleEngine/ExpressionBuilder.cs @@ -121,14 +121,22 @@ private static Expression BuildExpression(Type type, Rule rule, Expression expre { var elementType = ElementType(propType); var lambdaParam = Expression.Parameter(elementType, "lambdaParam"); - return Expression.Call( - typeof(Enumerable).GetMethods().First(m => - m.Name == rule.Operator - && m.GetParameters().Count() == 2 - ).MakeGenericMethod(elementType), - propExpression, - Expression.Lambda(Build(elementType, rule.Rules, lambdaParam, ExpressionType.AndAlso, true), lambdaParam) - ); + return rule?.Rules?.Any() == true + ? Expression.Call( + typeof(Enumerable).GetMethods().First(m => + m.Name == rule.Operator + && m.GetParameters().Count() == 2 + ).MakeGenericMethod(elementType), + propExpression, + Expression.Lambda(Build(elementType, rule.Rules, lambdaParam, ExpressionType.AndAlso, true), lambdaParam) + ) + : Expression.Call( + typeof(Enumerable).GetMethods().First(m => + m.Name == rule.Operator + && m.GetParameters().Count() == 1 + ).MakeGenericMethod(elementType), + propExpression + ); } // Invoke a method on the Property var inputs = rule.Inputs.Select(x => x.GetType()).ToArray();