Skip to content

Commit b1264a2

Browse files
authored
[Bug Fix] : Methods on custom objects not available due to System.Linq.Dynamic.Core update (#661)
* short-circuiting based on toggle * unit test cases fix * demoprogram * coverage * removal of extra comma * version update
1 parent e356e7d commit b1264a2

File tree

8 files changed

+205
-184
lines changed

8 files changed

+205
-184
lines changed

.github/workflows/dotnetcore-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434

3535
- name: Check Coverage
3636
shell: pwsh
37-
run: ./scripts/check-coverage.ps1 -reportPath coveragereport/Cobertura.xml -threshold 93
37+
run: ./scripts/check-coverage.ps1 -reportPath coveragereport/Cobertura.xml -threshold 92
3838

3939
- name: Coveralls GitHub Action
4040
uses: coverallsapp/[email protected]

demo/DemoApp/Program.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
// Copyright (c) Microsoft Corporation.
2-
// Licensed under the MIT License.
3-
4-
namespace DemoApp
5-
{
6-
public static class Program
7-
{
8-
public static void Main(string[] args)
9-
{
10-
new BasicDemo().Run();
11-
new JSONDemo().Run();
12-
new NestedInputDemo().Run();
13-
new EFDemo().Run();
14-
}
15-
}
16-
}
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace DemoApp
5+
{
6+
public static class Program
7+
{
8+
public static void Main(string[] args)
9+
{
10+
new BasicDemo().Run();
11+
new JSONDemo().Run();
12+
new NestedInputDemo().Run();
13+
new EFDemo().Run();
14+
}
15+
}
16+
}
Lines changed: 164 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,44 @@
1-
// Copyright (c) Microsoft Corporation.
2-
// Licensed under the MIT License.
3-
4-
using FastExpressionCompiler;
5-
using RulesEngine.HelperFunctions;
6-
using RulesEngine.Models;
7-
using System;
8-
using System.Collections.Generic;
9-
using System.Linq;
10-
using System.Linq.Dynamic.Core;
11-
using System.Linq.Dynamic.Core.Exceptions;
12-
using System.Linq.Dynamic.Core.Parser;
13-
using System.Linq.Expressions;
14-
using System.Reflection;
15-
using System.Text.RegularExpressions;
16-
17-
namespace RulesEngine.ExpressionBuilders
18-
{
19-
public class RuleExpressionParser
20-
{
21-
private readonly ReSettings _reSettings;
22-
private readonly IDictionary<string, MethodInfo> _methodInfo;
23-
24-
public RuleExpressionParser(ReSettings reSettings = null)
25-
{
26-
_reSettings = reSettings ?? new ReSettings();
27-
_methodInfo = new Dictionary<string, MethodInfo>();
28-
PopulateMethodInfo();
29-
}
30-
31-
private void PopulateMethodInfo()
32-
{
33-
var dict_add = typeof(Dictionary<string, object>).GetMethod("Add", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string), typeof(object) }, null);
34-
_methodInfo.Add("dict_add", dict_add);
35-
}
36-
public Expression Parse(string expression, ParameterExpression[] parameters, Type returnType)
37-
{
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using FastExpressionCompiler;
5+
using RulesEngine.HelperFunctions;
6+
using RulesEngine.Models;
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Linq;
10+
using System.Linq.Dynamic.Core;
11+
using System.Linq.Dynamic.Core.Exceptions;
12+
using System.Linq.Dynamic.Core.Parser;
13+
using System.Linq.Expressions;
14+
using System.Reflection;
15+
using System.Text.RegularExpressions;
16+
17+
namespace RulesEngine.ExpressionBuilders
18+
{
19+
public class RuleExpressionParser
20+
{
21+
private readonly ReSettings _reSettings;
22+
private readonly IDictionary<string, MethodInfo> _methodInfo;
23+
24+
public RuleExpressionParser(ReSettings reSettings = null)
25+
{
26+
_reSettings = reSettings ?? new ReSettings();
27+
_methodInfo = new Dictionary<string, MethodInfo>();
28+
PopulateMethodInfo();
29+
}
30+
31+
private void PopulateMethodInfo()
32+
{
33+
var dict_add = typeof(Dictionary<string, object>).GetMethod("Add", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string), typeof(object) }, null);
34+
_methodInfo.Add("dict_add", dict_add);
35+
}
36+
37+
public Expression Parse(string expression, ParameterExpression[] parameters, Type returnType)
38+
{
3839
var config = new ParsingConfig {
39-
CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes),
40-
IsCaseSensitive = _reSettings.IsExpressionCaseSensitive
40+
CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes),
41+
IsCaseSensitive = _reSettings.IsExpressionCaseSensitive
4142
};
4243

4344
// Instead of immediately returning default values, allow for expression parsing to handle dynamic evaluation.
@@ -47,14 +48,18 @@ public Expression Parse(string expression, ParameterExpression[] parameters, Typ
4748
}
4849
catch (ParseException)
4950
{
51+
if (_reSettings.EnableExceptionAsErrorMessageForRuleExpressionParsing)
52+
{
53+
throw;
54+
}
5055
return Expression.Constant(GetDefaultValueForType(returnType));
5156
}
52-
catch (Exception ex)
57+
catch (Exception)
5358
{
54-
throw new Exception($"Expression parsing error: {ex.Message}", ex);
55-
}
56-
}
57-
59+
throw;
60+
}
61+
}
62+
5863
private object GetDefaultValueForType(Type type)
5964
{
6065
if (type == typeof(bool))
@@ -63,124 +68,124 @@ private object GetDefaultValueForType(Type type)
6368
return int.MinValue;
6469
return null;
6570
}
66-
67-
public Func<object[], T> Compile<T>(string expression, RuleParameter[] ruleParams)
68-
{
69-
var rtype = typeof(T);
70-
if (rtype == typeof(object))
71-
{
72-
rtype = null;
73-
}
71+
72+
public Func<object[], T> Compile<T>(string expression, RuleParameter[] ruleParams)
73+
{
74+
var rtype = typeof(T);
75+
if (rtype == typeof(object))
76+
{
77+
rtype = null;
78+
}
7479
var parameterExpressions = GetParameterExpression(ruleParams).ToArray();
7580

76-
var e = Parse(expression, parameterExpressions, rtype);
77-
if (rtype == null)
78-
{
79-
e = Expression.Convert(e, typeof(T));
80-
}
81-
var expressionBody = new List<Expression>() { e };
82-
var wrappedExpression = WrapExpression<T>(expressionBody, parameterExpressions, new ParameterExpression[] { });
83-
return CompileExpression(wrappedExpression);
84-
85-
}
86-
87-
private Func<object[], T> CompileExpression<T>(Expression<Func<object[], T>> expression)
88-
{
89-
if (_reSettings.UseFastExpressionCompiler)
90-
{
91-
return expression.CompileFast();
92-
}
93-
return expression.Compile();
94-
}
95-
96-
private Expression<Func<object[], T>> WrapExpression<T>(List<Expression> expressionList, ParameterExpression[] parameters, ParameterExpression[] variables)
97-
{
98-
var argExp = Expression.Parameter(typeof(object[]), "args");
99-
var paramExps = parameters.Select((c, i) => {
100-
var arg = Expression.ArrayAccess(argExp, Expression.Constant(i));
101-
return (Expression)Expression.Assign(c, Expression.Convert(arg, c.Type));
102-
});
103-
var blockExpSteps = paramExps.Concat(expressionList);
104-
var blockExp = Expression.Block(parameters.Concat(variables), blockExpSteps);
105-
return Expression.Lambda<Func<object[], T>>(blockExp, argExp);
106-
}
107-
108-
internal Func<object[], Dictionary<string, object>> CompileRuleExpressionParameters(RuleParameter[] ruleParams, RuleExpressionParameter[] ruleExpParams = null)
109-
{
110-
ruleExpParams = ruleExpParams ?? new RuleExpressionParameter[] { };
111-
var expression = CreateDictionaryExpression(ruleParams, ruleExpParams);
112-
return CompileExpression(expression);
113-
}
114-
115-
public T Evaluate<T>(string expression, RuleParameter[] ruleParams)
81+
var e = Parse(expression, parameterExpressions, rtype);
82+
if (rtype == null)
83+
{
84+
e = Expression.Convert(e, typeof(T));
85+
}
86+
var expressionBody = new List<Expression>() { e };
87+
var wrappedExpression = WrapExpression<T>(expressionBody, parameterExpressions, new ParameterExpression[] { });
88+
return CompileExpression(wrappedExpression);
89+
90+
}
91+
92+
private Func<object[], T> CompileExpression<T>(Expression<Func<object[], T>> expression)
11693
{
117-
var func = Compile<T>(expression, ruleParams);
118-
return func(ruleParams.Select(c => c.Value).ToArray());
119-
}
120-
121-
private IEnumerable<Expression> CreateAssignedParameterExpression(RuleExpressionParameter[] ruleExpParams)
122-
{
123-
return ruleExpParams.Select((c, i) => {
124-
return Expression.Assign(c.ParameterExpression, c.ValueExpression);
125-
});
126-
}
127-
128-
// <summary>
129-
/// Gets the parameter expression.
130-
/// </summary>
131-
/// <param name="ruleParams">The types.</param>
132-
/// <returns></returns>
133-
/// <exception cref="ArgumentException">
134-
/// types
135-
/// or
136-
/// type
137-
/// </exception>
138-
private IEnumerable<ParameterExpression> GetParameterExpression(RuleParameter[] ruleParams)
139-
{
140-
foreach (var ruleParam in ruleParams)
141-
{
142-
if (ruleParam == null)
143-
{
144-
throw new ArgumentException($"{nameof(ruleParam)} can't be null.");
145-
}
146-
147-
yield return ruleParam.ParameterExpression;
148-
}
149-
}
150-
151-
private Expression<Func<object[], Dictionary<string, object>>> CreateDictionaryExpression(RuleParameter[] ruleParams, RuleExpressionParameter[] ruleExpParams)
152-
{
153-
var body = new List<Expression>();
154-
var paramExp = new List<ParameterExpression>();
155-
var variableExp = new List<ParameterExpression>();
156-
157-
158-
var variableExpressions = CreateAssignedParameterExpression(ruleExpParams);
159-
160-
body.AddRange(variableExpressions);
161-
162-
var dict = Expression.Variable(typeof(Dictionary<string, object>));
163-
var add = _methodInfo["dict_add"];
164-
165-
body.Add(Expression.Assign(dict, Expression.New(typeof(Dictionary<string, object>))));
166-
variableExp.Add(dict);
167-
168-
for (var i = 0; i < ruleParams.Length; i++)
169-
{
170-
paramExp.Add(ruleParams[i].ParameterExpression);
171-
}
172-
for (var i = 0; i < ruleExpParams.Length; i++)
173-
{
174-
var key = Expression.Constant(ruleExpParams[i].ParameterExpression.Name);
175-
var value = Expression.Convert(ruleExpParams[i].ParameterExpression, typeof(object));
176-
variableExp.Add(ruleExpParams[i].ParameterExpression);
94+
if (_reSettings.UseFastExpressionCompiler)
95+
{
96+
return expression.CompileFast();
97+
}
98+
return expression.Compile();
99+
}
100+
101+
private Expression<Func<object[], T>> WrapExpression<T>(List<Expression> expressionList, ParameterExpression[] parameters, ParameterExpression[] variables)
102+
{
103+
var argExp = Expression.Parameter(typeof(object[]), "args");
104+
var paramExps = parameters.Select((c, i) => {
105+
var arg = Expression.ArrayAccess(argExp, Expression.Constant(i));
106+
return (Expression)Expression.Assign(c, Expression.Convert(arg, c.Type));
107+
});
108+
var blockExpSteps = paramExps.Concat(expressionList);
109+
var blockExp = Expression.Block(parameters.Concat(variables), blockExpSteps);
110+
return Expression.Lambda<Func<object[], T>>(blockExp, argExp);
111+
}
112+
113+
internal Func<object[], Dictionary<string, object>> CompileRuleExpressionParameters(RuleParameter[] ruleParams, RuleExpressionParameter[] ruleExpParams = null)
114+
{
115+
ruleExpParams = ruleExpParams ?? new RuleExpressionParameter[] { };
116+
var expression = CreateDictionaryExpression(ruleParams, ruleExpParams);
117+
return CompileExpression(expression);
118+
}
119+
120+
public T Evaluate<T>(string expression, RuleParameter[] ruleParams)
121+
{
122+
var func = Compile<T>(expression, ruleParams);
123+
return func(ruleParams.Select(c => c.Value).ToArray());
124+
}
125+
126+
private IEnumerable<Expression> CreateAssignedParameterExpression(RuleExpressionParameter[] ruleExpParams)
127+
{
128+
return ruleExpParams.Select((c, i) => {
129+
return Expression.Assign(c.ParameterExpression, c.ValueExpression);
130+
});
131+
}
132+
133+
// <summary>
134+
/// Gets the parameter expression.
135+
/// </summary>
136+
/// <param name="ruleParams">The types.</param>
137+
/// <returns></returns>
138+
/// <exception cref="ArgumentException">
139+
/// types
140+
/// or
141+
/// type
142+
/// </exception>
143+
private IEnumerable<ParameterExpression> GetParameterExpression(RuleParameter[] ruleParams)
144+
{
145+
foreach (var ruleParam in ruleParams)
146+
{
147+
if (ruleParam == null)
148+
{
149+
throw new ArgumentException($"{nameof(ruleParam)} can't be null.");
150+
}
151+
152+
yield return ruleParam.ParameterExpression;
153+
}
154+
}
155+
156+
private Expression<Func<object[], Dictionary<string, object>>> CreateDictionaryExpression(RuleParameter[] ruleParams, RuleExpressionParameter[] ruleExpParams)
157+
{
158+
var body = new List<Expression>();
159+
var paramExp = new List<ParameterExpression>();
160+
var variableExp = new List<ParameterExpression>();
161+
162+
163+
var variableExpressions = CreateAssignedParameterExpression(ruleExpParams);
164+
165+
body.AddRange(variableExpressions);
166+
167+
var dict = Expression.Variable(typeof(Dictionary<string, object>));
168+
var add = _methodInfo["dict_add"];
169+
170+
body.Add(Expression.Assign(dict, Expression.New(typeof(Dictionary<string, object>))));
171+
variableExp.Add(dict);
172+
173+
for (var i = 0; i < ruleParams.Length; i++)
174+
{
175+
paramExp.Add(ruleParams[i].ParameterExpression);
176+
}
177+
for (var i = 0; i < ruleExpParams.Length; i++)
178+
{
179+
var key = Expression.Constant(ruleExpParams[i].ParameterExpression.Name);
180+
var value = Expression.Convert(ruleExpParams[i].ParameterExpression, typeof(object));
181+
variableExp.Add(ruleExpParams[i].ParameterExpression);
177182
body.Add(Expression.Call(dict, add, key, value));
178183

179-
}
180-
// Return value
181-
body.Add(dict);
182-
183-
return WrapExpression<Dictionary<string, object>>(body, paramExp.ToArray(), variableExp.ToArray());
184-
}
185-
}
184+
}
185+
// Return value
186+
body.Add(dict);
187+
188+
return WrapExpression<Dictionary<string, object>>(body, paramExp.ToArray(), variableExp.ToArray());
189+
}
190+
}
186191
}

0 commit comments

Comments
 (0)