perf improvements and benchmarking (#70)
* added perf improvements * Added benchmark toolpull/75/head v3.0.0-preview.2
parent
7b05b0a656
commit
a90880f126
24
CHANGELOG.md
24
CHANGELOG.md
|
@ -1,10 +1,34 @@
|
|||
# CHANGELOG
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [3.0.0-preview.2]
|
||||
- Made LocalParams and ErrorMessage formatting optional via ReSettings
|
||||
- Major performance improvement
|
||||
- 25% improvement from previous version
|
||||
- Upto 35% improvement by disabling optional features
|
||||
|
||||
## [3.0.0-preview.1] - 23-10-2020
|
||||
- Renamed `ExecuteRule` to `ExecuteAllRulesAsync`
|
||||
- Added Actions support. More details on [actions wiki](https://github.com/microsoft/RulesEngine/wiki/Actions)
|
||||
|
||||
## [2.1.5] - 02-11-2020
|
||||
- Added `Properties` field to Rule to allow custom fields to Rule
|
||||
|
||||
## [2.1.4] - 15-10-2020
|
||||
- Added exception data properties to identify RuleName.
|
||||
|
||||
## [2.1.3] - 12-10-2020
|
||||
- Optional parameter for rethrow exception on failure of expression compilation.
|
||||
|
||||
## [2.1.2] - 02-10-2020
|
||||
- Fixed binary expression requirement. Now any expression will work as long as it evalutes to boolean.
|
||||
|
||||
## [2.1.1] - 01-09-2020
|
||||
- Fixed exception thrown when errormessage field is null
|
||||
- Added better messaging when identifier is not found in expression
|
||||
- Fixed other minor bugs
|
||||
|
||||
## [2.1.0] - 18-05-2020
|
||||
- Adding local param support to make expression authroing more intuitive.
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29123.89
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RulesEngine", "src\RulesEngine\\RulesEngine.csproj", "{CD4DFE6A-083B-478E-8377-77F474833E30}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RulesEngine", "src\RulesEngine\RulesEngine.csproj", "{CD4DFE6A-083B-478E-8377-77F474833E30}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RulesEngine.UnitTest", "test\RulesEngine.UnitTest\RulesEngine.UnitTest.csproj", "{50E0C2A5-E2C8-4B12-8C0E-B69F698A82BF}"
|
||||
EndProject
|
||||
|
@ -17,6 +17,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
global.json = global.json
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RulesEngineBenchmark", "benchmark\RulesEngineBenchmark\RulesEngineBenchmark.csproj", "{C058809F-C720-4EFC-925D-A486627B238B}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{CD4DFE6A-083B-478E-8377-77F474833E30} = {CD4DFE6A-083B-478E-8377-77F474833E30}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -35,6 +40,10 @@ Global
|
|||
{57BB8C07-799A-4F87-A7CC-D3D3F694DD02}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{57BB8C07-799A-4F87-A7CC-D3D3F694DD02}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{57BB8C07-799A-4F87-A7CC-D3D3F694DD02}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C058809F-C720-4EFC-925D-A486627B238B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C058809F-C720-4EFC-925D-A486627B238B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C058809F-C720-4EFC-925D-A486627B238B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C058809F-C720-4EFC-925D-A486627B238B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Running;
|
||||
using Newtonsoft.Json;
|
||||
using RulesEngine.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace RulesEngineBenchmark
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class REBenchmark
|
||||
{
|
||||
private readonly RulesEngine.RulesEngine rulesEngine;
|
||||
private readonly object ruleInput;
|
||||
private readonly List<WorkflowRules> workflows;
|
||||
class ListItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public REBenchmark()
|
||||
{
|
||||
var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "NestedInputDemo.json", SearchOption.AllDirectories);
|
||||
if (files == null || files.Length == 0)
|
||||
throw new Exception("Rules not found.");
|
||||
|
||||
var fileData = File.ReadAllText(files[0]);
|
||||
workflows = JsonConvert.DeserializeObject<List<WorkflowRules>>(fileData);
|
||||
|
||||
rulesEngine = new RulesEngine.RulesEngine(workflows.ToArray(), null,new ReSettings {
|
||||
EnableFormattedErrorMessage = false,
|
||||
EnableLocalParams = false
|
||||
});
|
||||
|
||||
ruleInput = new
|
||||
{
|
||||
SimpleProp = "simpleProp",
|
||||
NestedProp = new
|
||||
{
|
||||
SimpleProp = "nestedSimpleProp",
|
||||
ListProp = new List<ListItem>
|
||||
{
|
||||
new ListItem
|
||||
{
|
||||
Id = 1,
|
||||
Value = "first"
|
||||
},
|
||||
new ListItem
|
||||
{
|
||||
Id = 2,
|
||||
Value = "second"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
[Params(1000, 10000)]
|
||||
public int N;
|
||||
|
||||
[Benchmark]
|
||||
public void RuleExecutionDefault()
|
||||
{
|
||||
foreach (var workflow in workflows)
|
||||
{
|
||||
List<RuleResultTree> resultList = rulesEngine.ExecuteAllRulesAsync(workflow.WorkflowName, ruleInput).Result;
|
||||
}
|
||||
}
|
||||
}
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
var summary = BenchmarkRunner.Run<REBenchmark>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\RulesEngine\RulesEngine.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Workflows\Discount.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Workflows\NestedInputDemo.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,92 @@
|
|||
[
|
||||
{
|
||||
"WorkflowName": "Discount",
|
||||
"Rules": [
|
||||
{
|
||||
"RuleName": "GiveDiscount10",
|
||||
"SuccessEvent": "10",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input1.country == \"india\" AND input1.loyalityFactor <= 2 AND input1.totalPurchasesToDate >= 5000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
|
||||
},
|
||||
{
|
||||
"RuleName": "GiveDiscount20",
|
||||
"SuccessEvent": "20",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input1.country == \"india\" AND input1.loyalityFactor == 3 AND input1.totalPurchasesToDate >= 10000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
|
||||
},
|
||||
{
|
||||
"RuleName": "GiveDiscount25",
|
||||
"SuccessEvent": "25",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input1.country != \"india\" AND input1.loyalityFactor >= 2 AND input1.totalPurchasesToDate >= 10000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 5"
|
||||
},
|
||||
{
|
||||
"RuleName": "GiveDiscount30",
|
||||
"SuccessEvent": "30",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input1.loyalityFactor > 3 AND input1.totalPurchasesToDate >= 50000 AND input1.totalPurchasesToDate <= 100000 AND input2.totalOrders > 5 AND input3.noOfVisitsPerMonth > 15"
|
||||
},
|
||||
{
|
||||
"RuleName": "GiveDiscount30NestedOrExample",
|
||||
"SuccessEvent": "30",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"Operator": "OrElse",
|
||||
"Rules":[
|
||||
{
|
||||
"RuleName": "IsLoyalAndHasGoodSpend",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input1.loyalityFactor > 3 AND input1.totalPurchasesToDate >= 50000 AND input1.totalPurchasesToDate <= 100000"
|
||||
},
|
||||
{
|
||||
"RuleName": "OrHasHighNumberOfTotalOrders",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input2.totalOrders > 15"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"RuleName": "GiveDiscount35NestedAndExample",
|
||||
"SuccessEvent": "35",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"Operator": "AndAlso",
|
||||
"Rules": [
|
||||
{
|
||||
"RuleName": "IsLoyal",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input1.loyalityFactor > 3"
|
||||
},
|
||||
{
|
||||
"RuleName": "AndHasTotalPurchased100000",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input1.totalPurchasesToDate >= 100000"
|
||||
},
|
||||
{
|
||||
"RuleName": "AndOtherConditions",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input2.totalOrders > 15 AND input3.noOfVisitsPerMonth > 25"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,39 @@
|
|||
[
|
||||
{
|
||||
"WorkflowName": "NestedInputDemoWorkflow1",
|
||||
"Rules": [
|
||||
{
|
||||
"RuleName": "CheckNestedSimpleProp",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input1.NestedProp.SimpleProp == \"nestedSimpleProp\""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"WorkflowName": "NestedInputDemoWorkflow2",
|
||||
"Rules": [
|
||||
{
|
||||
"RuleName": "CheckNestedListProp",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input1.NestedProp.ListProp[0].Id == 1 && input1.NestedProp.ListProp[1].Value == \"second\""
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"WorkflowName": "NestedInputDemoWorkflow3",
|
||||
"Rules": [
|
||||
{
|
||||
"RuleName": "CheckNestedListPropFunctions",
|
||||
"ErrorMessage": "One or more adjust rules failed.",
|
||||
"ErrorType": "Error",
|
||||
"RuleExpressionType": "LambdaExpression",
|
||||
"Expression": "input1.NestedProp.ListProp[1].Value.ToUpper() = \"SECOND\""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -17,7 +17,7 @@ namespace RulesEngine.Actions
|
|||
public override ValueTask<object> Run(ActionContext context, RuleParameter[] ruleParameters)
|
||||
{
|
||||
var expression = context.GetContext<string>("expression");
|
||||
return new ValueTask<object>(_ruleExpressionParser.Evaluate(expression, ruleParameters));
|
||||
return new ValueTask<object>(_ruleExpressionParser.Evaluate<object>(expression, ruleParameters));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,8 +25,8 @@ namespace RulesEngine.ExpressionBuilders
|
|||
{
|
||||
try
|
||||
{
|
||||
var ruleDelegate = _ruleExpressionParser.Compile(rule.Expression, ruleParams,typeof(bool));
|
||||
bool func(object[] paramList) => (bool)ruleDelegate.DynamicInvoke(paramList);
|
||||
var ruleDelegate = _ruleExpressionParser.Compile<bool>(rule.Expression, ruleParams);
|
||||
bool func(object[] paramList) => ruleDelegate(paramList);
|
||||
return Helpers.ToResultTree(rule, null, func);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Linq.Dynamic.Core;
|
||||
using System.Linq.Expressions;
|
||||
using FastExpressionCompiler;
|
||||
|
||||
namespace RulesEngine.ExpressionBuilders
|
||||
{
|
||||
|
@ -21,23 +22,34 @@ namespace RulesEngine.ExpressionBuilders
|
|||
});
|
||||
}
|
||||
|
||||
public Delegate Compile(string expression, RuleParameter[] ruleParams, Type returnType = null)
|
||||
public Func<object[],T> Compile<T>(string expression, RuleParameter[] ruleParams)
|
||||
{
|
||||
var cacheKey = GetCacheKey(expression,ruleParams,returnType);
|
||||
var cacheKey = GetCacheKey(expression,ruleParams,typeof(T));
|
||||
return _memoryCache.GetOrCreate(cacheKey,(entry) => {
|
||||
entry.SetSize(1);
|
||||
var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) };
|
||||
var typeParamExpressions = GetParameterExpression(ruleParams).ToArray();
|
||||
var e = DynamicExpressionParser.ParseLambda(config, true, typeParamExpressions.ToArray(), returnType, expression);
|
||||
return e.Compile();
|
||||
var e = DynamicExpressionParser.ParseLambda(config, true, typeParamExpressions.ToArray(), typeof(T), expression);
|
||||
var wrappedExpression = WrapExpression<T>(e,typeParamExpressions);
|
||||
return wrappedExpression.CompileFast<Func<object[],T>>();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public object Evaluate(string expression, RuleParameter[] ruleParams, Type returnType = null)
|
||||
private Expression<Func<object[],T>> WrapExpression<T>(Expression expression, ParameterExpression[] parameters){
|
||||
var argExp = Expression.Parameter(typeof(object[]),"args");
|
||||
var paramExps = parameters.Select((c,i) => {
|
||||
var arg = Expression.ArrayAccess(argExp,Expression.Constant(i));
|
||||
return Expression.Convert(arg,c.Type);
|
||||
});
|
||||
var invokeExp = Expression.Invoke(expression,paramExps);
|
||||
return Expression.Lambda<Func<object[],T>>(invokeExp, argExp);
|
||||
}
|
||||
|
||||
public T Evaluate<T>(string expression, RuleParameter[] ruleParams)
|
||||
{
|
||||
var func = Compile(expression, ruleParams, returnType);
|
||||
return func.DynamicInvoke(ruleParams.Select(c => c.Value).ToArray());
|
||||
var func = Compile<T>(expression, ruleParams);
|
||||
return func(ruleParams.Select(c => c.Value).ToArray());
|
||||
}
|
||||
|
||||
// <summary>
|
||||
|
|
|
@ -11,33 +11,12 @@ namespace RulesEngine.Models
|
|||
[ExcludeFromCodeCoverage]
|
||||
internal class CompiledParam
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
internal string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The value.
|
||||
/// </value>
|
||||
internal Delegate Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parameters.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The parameters.
|
||||
/// </value>
|
||||
internal IEnumerable<RuleParameter> Parameters { get; set; }
|
||||
|
||||
internal Type ReturnType { get; set; }
|
||||
internal Func<object[],object> Value { get; set; }
|
||||
internal RuleParameter AsRuleParameter()
|
||||
{
|
||||
return new RuleParameter(Name,Value.Method.ReturnType);
|
||||
return new RuleParameter(Name,ReturnType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@ namespace RulesEngine.Models
|
|||
public class ReSettings
|
||||
{
|
||||
public Type[] CustomTypes { get; set; }
|
||||
|
||||
public Dictionary<string, Func<ActionBase>> CustomActions { get; set; }
|
||||
|
||||
public bool EnableExceptionAsErrorMessage { get; set; } = true;
|
||||
public bool EnableFormattedErrorMessage {get; set; } = true;
|
||||
public bool EnableLocalParams {get;set;} = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,13 +17,12 @@ namespace RulesEngine.Models
|
|||
public class Rule
|
||||
{
|
||||
public string RuleName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom property or tags of the rule.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The properties of the rule.
|
||||
/// </value>
|
||||
/// <summary>
|
||||
/// Gets or sets the custom property or tags of the rule.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The properties of the rule.
|
||||
/// </value>
|
||||
public Dictionary<string, object> Properties { get; set; }
|
||||
public string Operator { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using RulesEngine.Models;
|
||||
using RulesEngine.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using RulesEngine.ExpressionBuilders;
|
||||
using RulesEngine.HelperFunctions;
|
||||
|
||||
namespace RulesEngine
|
||||
{
|
||||
|
@ -41,9 +39,9 @@ namespace RulesEngine
|
|||
var evaluatedParameters = new List<RuleParameter>();
|
||||
foreach (var param in rule.LocalParams)
|
||||
{
|
||||
var compiledParam = GetDelegateForRuleParam(param, ruleParams.ToArray());
|
||||
compiledParameters.Add(new CompiledParam { Name = param.Name, Value = compiledParam, Parameters = evaluatedParameters });
|
||||
var evaluatedParam = EvaluateCompiledParam(param.Name, compiledParam, ruleParams);
|
||||
var compiledParamDelegate = GetDelegateForRuleParam(param, ruleParams.ToArray());
|
||||
var evaluatedParam = EvaluateCompiledParam(param.Name, compiledParamDelegate, ruleParams);
|
||||
compiledParameters.Add(new CompiledParam { Name = param.Name, Value = compiledParamDelegate, ReturnType = evaluatedParam.Type });
|
||||
ruleParams = ruleParams.Append(evaluatedParam);
|
||||
evaluatedParameters.Add(evaluatedParam);
|
||||
}
|
||||
|
@ -56,9 +54,9 @@ namespace RulesEngine
|
|||
/// <param name="compiledParam">The compiled parameter.</param>
|
||||
/// <param name="ruleParams">The rule parameters.</param>
|
||||
/// <returns>RuleParameter.</returns>
|
||||
public RuleParameter EvaluateCompiledParam(string paramName, Delegate compiledParam, IEnumerable<RuleParameter> inputs)
|
||||
public RuleParameter EvaluateCompiledParam(string paramName, Func<object[],object> compiledParam, IEnumerable<RuleParameter> inputs)
|
||||
{
|
||||
var result = compiledParam.DynamicInvoke(inputs.Select(c => c.Value).ToArray());
|
||||
var result = compiledParam(inputs.Select(c => c.Value).ToArray());
|
||||
return new RuleParameter(paramName, result);
|
||||
}
|
||||
|
||||
|
@ -70,9 +68,9 @@ namespace RulesEngine
|
|||
/// <param name="typeParameterExpressions">The type parameter expressions.</param>
|
||||
/// <param name="ruleInputExp">The rule input exp.</param>
|
||||
/// <returns></returns>
|
||||
private Delegate GetDelegateForRuleParam(LocalParam param, RuleParameter[] ruleParameters)
|
||||
private Func<object[],object> GetDelegateForRuleParam(LocalParam param, RuleParameter[] ruleParameters)
|
||||
{
|
||||
return _ruleExpressionParser.Compile(param.Expression, ruleParameters);
|
||||
return _ruleExpressionParser.Compile<object>(param.Expression, ruleParameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ namespace RulesEngine
|
|||
{
|
||||
internal class RuleExpressionBuilderFactory
|
||||
{
|
||||
private ReSettings _reSettings;
|
||||
private LambdaExpressionBuilder _lambdaExpressionBuilder;
|
||||
private readonly ReSettings _reSettings;
|
||||
private readonly LambdaExpressionBuilder _lambdaExpressionBuilder;
|
||||
public RuleExpressionBuilderFactory(ReSettings reSettings, RuleExpressionParser expressionParser)
|
||||
{
|
||||
_reSettings = reSettings;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System.Threading.Tasks;
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -234,7 +234,7 @@ namespace RulesEngine
|
|||
if (workflowRules != null)
|
||||
{
|
||||
var dictFunc = new Dictionary<string,RuleFunc<RuleResultTree>>();
|
||||
foreach (var rule in _rulesCache.GetRules(workflowName))
|
||||
foreach (var rule in workflowRules.Rules)
|
||||
{
|
||||
dictFunc.Add(rule.RuleName,CompileRule(workflowName, ruleParams, rule));
|
||||
}
|
||||
|
@ -261,6 +261,9 @@ namespace RulesEngine
|
|||
|
||||
private RuleFunc<RuleResultTree> CompileRule(string workflowName, RuleParameter[] ruleParams, Rule rule)
|
||||
{
|
||||
if(!_reSettings.EnableLocalParams){
|
||||
return _ruleCompiler.CompileRule(rule,ruleParams);
|
||||
}
|
||||
var compiledParamsKey = GetCompiledParamsCacheKey(workflowName, rule.RuleName, ruleParams);
|
||||
IEnumerable<CompiledParam> compiledParamList = _compiledParamsCache.GetOrCreate(compiledParamsKey, (entry) => _ruleParamCompiler.CompileParamsExpression(rule, ruleParams));
|
||||
var compiledRuleParameters = compiledParamList?.Select(c => c.AsRuleParameter()) ?? new List<RuleParameter>();
|
||||
|
@ -335,35 +338,36 @@ namespace RulesEngine
|
|||
/// <returns>Updated error message.</returns>
|
||||
private IEnumerable<RuleResultTree> FormatErrorMessages(IEnumerable<RuleResultTree> ruleResultList)
|
||||
{
|
||||
if(_reSettings.EnableFormattedErrorMessage){
|
||||
foreach (var ruleResult in ruleResultList?.Where(r => !r.IsSuccess))
|
||||
{
|
||||
var errorMessage = ruleResult?.Rule?.ErrorMessage;
|
||||
if(errorMessage != null){
|
||||
var errorParameters = Regex.Matches(errorMessage, ParamParseRegex);
|
||||
|
||||
foreach (var ruleResult in ruleResultList?.Where(r => !r.IsSuccess))
|
||||
{
|
||||
var errorMessage = ruleResult?.Rule?.ErrorMessage;
|
||||
if(errorMessage != null){
|
||||
var errorParameters = Regex.Matches(errorMessage, ParamParseRegex);
|
||||
|
||||
var inputs = ruleResult.Inputs;
|
||||
foreach (var param in errorParameters)
|
||||
{
|
||||
var paramVal = param?.ToString();
|
||||
var property = paramVal?.Substring(2, paramVal.Length - 3);
|
||||
if (property?.Split('.')?.Count() > 1)
|
||||
var inputs = ruleResult.Inputs;
|
||||
foreach (var param in errorParameters)
|
||||
{
|
||||
var typeName = property?.Split('.')?[0];
|
||||
var propertyName = property?.Split('.')?[1];
|
||||
errorMessage = UpdateErrorMessage(errorMessage, inputs, property, typeName, propertyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var arrParams = inputs?.Select(c => new { Name = c.Key, c.Value });
|
||||
var model = arrParams?.Where(a => string.Equals(a.Name, property))?.FirstOrDefault();
|
||||
var value = model?.Value != null ? JsonConvert.SerializeObject(model?.Value) : null;
|
||||
errorMessage = errorMessage?.Replace($"$({property})", value ?? $"$({property})");
|
||||
var paramVal = param?.ToString();
|
||||
var property = paramVal?.Substring(2, paramVal.Length - 3);
|
||||
if (property?.Split('.')?.Count() > 1)
|
||||
{
|
||||
var typeName = property?.Split('.')?[0];
|
||||
var propertyName = property?.Split('.')?[1];
|
||||
errorMessage = UpdateErrorMessage(errorMessage, inputs, property, typeName, propertyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var arrParams = inputs?.Select(c => new { Name = c.Key, c.Value });
|
||||
var model = arrParams?.Where(a => string.Equals(a.Name, property))?.FirstOrDefault();
|
||||
var value = model?.Value != null ? JsonConvert.SerializeObject(model?.Value) : null;
|
||||
errorMessage = errorMessage?.Replace($"$({property})", value ?? $"$({property})");
|
||||
}
|
||||
}
|
||||
ruleResult.ExceptionMessage = errorMessage;
|
||||
}
|
||||
ruleResult.ExceptionMessage = errorMessage;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return ruleResultList;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Version>3.0.0-preview.1</Version>
|
||||
<Version>3.0.0-preview.2</Version>
|
||||
<Copyright>Copyright (c) Microsoft Corporation.</Copyright>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageProjectUrl>https://github.com/microsoft/RulesEngine</PackageProjectUrl>
|
||||
|
@ -25,6 +25,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FastExpressionCompiler" Version="2.0.0" />
|
||||
<PackageReference Include="FluentValidation" Version="9.0.1" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.6" />
|
||||
|
|
|
@ -242,7 +242,7 @@ namespace RulesEngine.UnitTest
|
|||
|
||||
var fileData = File.ReadAllText(files[0]);
|
||||
var bre = new RulesEngine(JsonConvert.DeserializeObject<WorkflowRules[]>(fileData), null);
|
||||
var result = await bre.ExecuteAllRulesAsync("inputWorkflow", ruleParams?.ToArray()); ;
|
||||
var result = await bre.ExecuteAllRulesAsync("inputWorkflow", ruleParams?.ToArray());
|
||||
var ruleResult = result?.FirstOrDefault(r => string.Equals(r.Rule.RuleName, "GiveDiscount10", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.True(ruleResult.IsSuccess);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue