Entity Framework demonstration (#186)
* Initial Commit * WIP * Readability + null ef issue r&d * Cleanup * Minimum Sample * Refactored to remove EF from DemoApp * Fixed RuleValidator issue Added EmptyRulesTest for expected simple and nested rules empty exception Co-authored-by: Alex Reich <alex@alexreich.com> Co-authored-by: Alex Reich <Alex_Reich@mechanicsbank.com>pull/190/head
parent
b763f718bc
commit
9f898b703b
|
@ -26,6 +26,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RulesEngineBenchmark", "ben
|
|||
{CD4DFE6A-083B-478E-8377-77F474833E30} = {CD4DFE6A-083B-478E-8377-77F474833E30}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoApp.EFDataExample", "demo\DemoApp.EFDataExample\DemoApp.EFDataExample.csproj", "{E376D3E6-6890-4C09-9EA0-3EFD9C1E036D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -48,6 +50,10 @@ Global
|
|||
{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
|
||||
{E376D3E6-6890-4C09-9EA0-3EFD9C1E036D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E376D3E6-6890-4C09-9EA0-3EFD9C1E036D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E376D3E6-6890-4C09-9EA0-3EFD9C1E036D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E376D3E6-6890-4C09-9EA0-3EFD9C1E036D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<RootNamespace>DemoApp.EFDataExample</RootNamespace>
|
||||
<AssemblyName>DemoApp.EFDataExample</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\RulesEngine\RulesEngine.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,74 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json;
|
||||
using RulesEngine.Models;
|
||||
|
||||
namespace DemoApp.EFDataExample
|
||||
{
|
||||
public class RulesEngineDemoContext : DbContext
|
||||
{
|
||||
public DbSet<WorkflowRules> WorkflowRules { get; set; }
|
||||
public DbSet<ActionInfo> ActionInfos { get; set; }
|
||||
|
||||
public DbSet<RuleActions> RuleActions { get; set; }
|
||||
public DbSet<Rule> Rules { get; set; }
|
||||
public DbSet<ScopedParam> ScopedParams { get; set; }
|
||||
|
||||
public string DbPath { get; private set; }
|
||||
|
||||
public RulesEngineDemoContext()
|
||||
{
|
||||
var folder = Environment.SpecialFolder.LocalApplicationData;
|
||||
var path = Environment.GetFolderPath(folder);
|
||||
DbPath = $"{path}{System.IO.Path.DirectorySeparatorChar}RulesEngineDemo.db";
|
||||
}
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
=> options.UseSqlite($"Data Source={DbPath}");
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<ActionInfo>()
|
||||
.Property(b => b.Context)
|
||||
.HasConversion(
|
||||
v => JsonConvert.SerializeObject(v),
|
||||
v => JsonConvert.DeserializeObject<Dictionary<string, object>>(v));
|
||||
|
||||
modelBuilder.Entity<ActionInfo>()
|
||||
.HasKey(k => k.Name);
|
||||
|
||||
modelBuilder.Entity<ScopedParam>()
|
||||
.HasKey(k => k.Name);
|
||||
|
||||
modelBuilder.Entity<WorkflowRules>(entity => {
|
||||
entity.HasKey(k => k.WorkflowName);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<RuleActions>(entity => {
|
||||
entity.HasNoKey();
|
||||
entity.HasOne(o => o.OnSuccess).WithMany();
|
||||
entity.HasOne(o => o.OnFailure).WithMany();
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Rule>(entity => {
|
||||
entity.HasKey(k => k.RuleName);
|
||||
|
||||
entity.Property(b => b.Properties)
|
||||
.HasConversion(
|
||||
v => JsonConvert.SerializeObject(v),
|
||||
v => JsonConvert.DeserializeObject<Dictionary<string, object>>(v));
|
||||
entity.Ignore(e => e.Actions);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<WorkflowRules>()
|
||||
.Ignore(b => b.WorkflowRulesToInject);
|
||||
|
||||
modelBuilder.Entity<Rule>()
|
||||
.Ignore(b => b.WorkflowRulesToInject);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -9,6 +9,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<ProjectReference Include="../../src/RulesEngine/RulesEngine.csproj" />
|
||||
<ProjectReference Include="..\DemoApp.EFDataExample\DemoApp.EFDataExample.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using DemoApp.EFDataExample;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using RulesEngine.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using static RulesEngine.Extensions.ListofRuleResultTreeExtension;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DemoApp
|
||||
{
|
||||
public class EFDemo
|
||||
{
|
||||
public void Run()
|
||||
{
|
||||
Console.WriteLine($"Running {nameof(EFDemo)}....");
|
||||
var basicInfo = "{\"name\": \"hello\",\"email\": \"abcy@xyz.com\",\"creditHistory\": \"good\",\"country\": \"canada\",\"loyalityFactor\": 3,\"totalPurchasesToDate\": 10000}";
|
||||
var orderInfo = "{\"totalOrders\": 5,\"recurringItems\": 2}";
|
||||
var telemetryInfo = "{\"noOfVisitsPerMonth\": 10,\"percentageOfBuyingToVisit\": 15}";
|
||||
|
||||
var converter = new ExpandoObjectConverter();
|
||||
|
||||
dynamic input1 = JsonConvert.DeserializeObject<ExpandoObject>(basicInfo, converter);
|
||||
dynamic input2 = JsonConvert.DeserializeObject<ExpandoObject>(orderInfo, converter);
|
||||
dynamic input3 = JsonConvert.DeserializeObject<ExpandoObject>(telemetryInfo, converter);
|
||||
|
||||
var inputs = new dynamic[]
|
||||
{
|
||||
input1,
|
||||
input2,
|
||||
input3
|
||||
};
|
||||
|
||||
var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "Discount.json", SearchOption.AllDirectories);
|
||||
if (files == null || files.Length == 0)
|
||||
throw new Exception("Rules not found.");
|
||||
|
||||
var fileData = File.ReadAllText(files[0]);
|
||||
var workflowRules = JsonConvert.DeserializeObject<List<WorkflowRules>>(fileData);
|
||||
|
||||
RulesEngineDemoContext db = new RulesEngineDemoContext();
|
||||
if (db.Database.EnsureCreated())
|
||||
{
|
||||
db.WorkflowRules.AddRange(workflowRules);
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
var wfr = db.WorkflowRules.Include(i => i.Rules).ThenInclude(i => i.Rules).ToArray();
|
||||
|
||||
var bre = new RulesEngine.RulesEngine(wfr, null);
|
||||
|
||||
string discountOffered = "No discount offered.";
|
||||
|
||||
List<RuleResultTree> resultList = bre.ExecuteAllRulesAsync("Discount", inputs).Result;
|
||||
|
||||
resultList.OnSuccess((eventName) => {
|
||||
discountOffered = $"Discount offered is {eventName} % over MRP.";
|
||||
});
|
||||
|
||||
resultList.OnFail(() => {
|
||||
discountOffered = "The user is not eligible for any discount.";
|
||||
});
|
||||
|
||||
Console.WriteLine(discountOffered);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ namespace DemoApp
|
|||
{
|
||||
new BasicDemo().Run();
|
||||
new NestedInputDemo().Run();
|
||||
new EFDemo().Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ namespace RulesEngine.Validators
|
|||
{
|
||||
When(c => c.Operator == null && c.RuleExpressionType == RuleExpressionType.LambdaExpression, () => {
|
||||
RuleFor(c => c.Expression).NotEmpty().WithMessage(Constants.LAMBDA_EXPRESSION_EXPRESSION_NULL_ERRMSG);
|
||||
RuleFor(c => c.Rules).Null().WithMessage(Constants.LAMBDA_EXPRESSION_RULES_ERRMSG);
|
||||
RuleFor(c => c.Rules).Empty().WithMessage(Constants.LAMBDA_EXPRESSION_RULES_ERRMSG);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using RulesEngine.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace RulesEngine.UnitTest
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class EmptyRulesTest
|
||||
{
|
||||
[Fact]
|
||||
private async Task EmptyRules_ReturnsExepectedResults()
|
||||
{
|
||||
var workflows = GetEmptyWorkflows();
|
||||
var reSettings = new ReSettings { };
|
||||
RulesEngine rulesEngine = new RulesEngine();
|
||||
|
||||
Func<Task> action = () => {
|
||||
new RulesEngine(workflows, reSettings: reSettings);
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
Exception ex = await Assert.ThrowsAsync<Exceptions.RuleValidationException>(action);
|
||||
|
||||
Assert.Contains("Atleast one of Rules or WorkflowRulesToInject must be not empty", ex.Message);
|
||||
}
|
||||
[Fact]
|
||||
private async Task NestedRulesWithEmptyNestedActions_ReturnsExepectedResults()
|
||||
{
|
||||
var workflows = GetEmptyNestedWorkflows();
|
||||
var reSettings = new ReSettings { };
|
||||
RulesEngine rulesEngine = new RulesEngine();
|
||||
|
||||
Func<Task> action = () => {
|
||||
new RulesEngine(workflows, reSettings: reSettings);
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
Exception ex = await Assert.ThrowsAsync<Exceptions.RuleValidationException>(action);
|
||||
|
||||
Assert.Contains("Atleast one of Rules or WorkflowRulesToInject must be not empty", ex.Message);
|
||||
}
|
||||
|
||||
private WorkflowRules[] GetEmptyWorkflows()
|
||||
{
|
||||
return new[] {
|
||||
new WorkflowRules {
|
||||
WorkflowName = "EmptyRulesTest",
|
||||
Rules = new Rule[] {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private WorkflowRules[] GetEmptyNestedWorkflows()
|
||||
{
|
||||
return new[] {
|
||||
new WorkflowRules {
|
||||
WorkflowName = "EmptyNestedRulesTest",
|
||||
Rules = new Rule[] {
|
||||
new Rule {
|
||||
RuleName = "AndRuleTrueFalse",
|
||||
Operator = "And",
|
||||
Rules = new Rule[] {
|
||||
new Rule{
|
||||
RuleName = "trueRule1",
|
||||
Expression = "input1.TrueValue == true",
|
||||
},
|
||||
new Rule {
|
||||
RuleName = "falseRule1",
|
||||
Expression = "input1.TrueValue == false"
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
new Rule {
|
||||
RuleName = "OrRuleTrueFalse",
|
||||
Operator = "Or",
|
||||
Rules = new Rule[] {
|
||||
new Rule{
|
||||
RuleName = "trueRule2",
|
||||
Expression = "input1.TrueValue == true",
|
||||
},
|
||||
new Rule {
|
||||
RuleName = "falseRule2",
|
||||
Expression = "input1.TrueValue == false"
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
new Rule {
|
||||
RuleName = "AndRuleFalseTrue",
|
||||
Operator = "And",
|
||||
Rules = new Rule[] {
|
||||
new Rule{
|
||||
RuleName = "trueRule3",
|
||||
Expression = "input1.TrueValue == false",
|
||||
},
|
||||
new Rule {
|
||||
RuleName = "falseRule4",
|
||||
Expression = "input1.TrueValue == true"
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
new Rule {
|
||||
RuleName = "OrRuleFalseTrue",
|
||||
Operator = "Or",
|
||||
Rules = new Rule[] {
|
||||
new Rule{
|
||||
RuleName = "trueRule3",
|
||||
Expression = "input1.TrueValue == false",
|
||||
},
|
||||
new Rule {
|
||||
RuleName = "falseRule4",
|
||||
Expression = "input1.TrueValue == true"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new WorkflowRules {
|
||||
WorkflowName = "EmptyNestedRulesActionsTest",
|
||||
Rules = new Rule[] {
|
||||
new Rule {
|
||||
RuleName = "AndRuleTrueFalse",
|
||||
Operator = "And",
|
||||
Rules = new Rule[] {
|
||||
|
||||
},
|
||||
Actions = new RuleActions {
|
||||
OnFailure = new ActionInfo{
|
||||
Name = "OutputExpression",
|
||||
Context = new Dictionary<string, object> {
|
||||
{ "Expression", "input1.TrueValue" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue