Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Entity Framework demonstration #186

Merged
merged 7 commits into from
Jul 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions RulesEngine.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
18 changes: 18 additions & 0 deletions demo/DemoApp.EFDataExample/DemoApp.EFDataExample.csproj
Original file line number Diff line number Diff line change
@@ -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>
74 changes: 74 additions & 0 deletions demo/DemoApp.EFDataExample/RulesEngineDemoContext.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}

}
1 change: 1 addition & 0 deletions demo/DemoApp/DemoApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
73 changes: 73 additions & 0 deletions demo/DemoApp/EFDemo.cs
Original file line number Diff line number Diff line change
@@ -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\": \"[email protected]\",\"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);
}
}
}
1 change: 1 addition & 0 deletions demo/DemoApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public static void Main(string[] args)
{
new BasicDemo().Run();
new NestedInputDemo().Run();
new EFDemo().Run();
}
}
}
2 changes: 1 addition & 1 deletion src/RulesEngine/Validators/RuleValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ private void RegisterExpressionTypeRules()
{
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);
});
}

Expand Down
156 changes: 156 additions & 0 deletions test/RulesEngine.UnitTest/EmptyRulesTest.cs
Original file line number Diff line number Diff line change
@@ -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" }
}
}
}
}
}
}

};
}
}
}