├── README.md
├── .gitignore
├── EngineTests
├── TestModelWithInterfaces
│ ├── Product2.cs
│ ├── IProduct.cs
│ ├── ICdsTrade.cs
│ ├── CdsTrade2.cs
│ ├── CdsTrade.cs
│ ├── ICreditDefaultSwap.cs
│ ├── CreditDefaultSwap.cs
│ ├── ITrade.cs
│ ├── CreditDefaultSwap2.cs
│ ├── Trade.cs
│ ├── Trade2.cs
│ ├── CdsRules2.cs
│ └── CdsRules.cs
├── TestModelWithoutInterfaces
│ ├── IProduct.cs
│ ├── CdsTrade.cs
│ ├── CreditDefaultSwap.cs
│ ├── Trade.cs
│ └── CdsRules.cs
├── IBingo.cs
├── IAbcd.cs
├── Bingo.cs
├── Xyz.cs
├── Abcd.cs
├── Dog.cs
├── XyzRules.cs
├── EngineTests.csproj
├── AbcdRules.cs
├── CompositeObjectsWithoutInterfacesTestFixture.cs
├── DogRules.cs
├── BingoRules.cs
├── DynamicWrapperTestFixture.cs
├── CompositeObjectsWithInterfacesTestFixture.cs
├── InterfaceWrapperTestFixture.cs
├── RulesEngineTestFixture.cs
├── UtilitiesTestFixture.cs
└── ExplainTestFixture.cs
├── .github
└── workflows
│ └── dotnetcore.yml
├── BusinessRulesEngine.sln.DotSettings
├── RulesEngine
├── RulesEngine
│ ├── Explain
│ │ ├── ExplainExpression.cs
│ │ ├── NullQueryable.cs
│ │ ├── NullExecutor.cs
│ │ ├── QueryOperator.cs
│ │ ├── ExplainOrExpression.cs
│ │ ├── ExplainAndExpression.cs
│ │ ├── Explain.cs
│ │ ├── LeafExpression.cs
│ │ └── QueryVisitor.cs
│ ├── Rule.cs
│ ├── MappingRules.cs
│ └── FluentExtensions.cs
├── RulesEngine.csproj
├── Tools
│ ├── RuntimeCompiler.cs
│ ├── ExpressionTreeHelper.cs
│ └── CompiledAccessors.cs
└── Interceptors
│ ├── InterfaceWrapper.cs
│ └── DynamicWrapper.cs
└── BusinessRulesEngine.sln
/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usinesoft/BusinessRulesEngine/HEAD/README.md
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usinesoft/BusinessRulesEngine/HEAD/.gitignore
--------------------------------------------------------------------------------
/EngineTests/TestModelWithInterfaces/Product2.cs:
--------------------------------------------------------------------------------
1 | namespace EngineTests.TestModelWithInterfaces
2 | {
3 | public abstract class Product2
4 | {
5 | }
6 | }
--------------------------------------------------------------------------------
/EngineTests/TestModelWithInterfaces/IProduct.cs:
--------------------------------------------------------------------------------
1 | namespace EngineTests.TestModelWithInterfaces
2 | {
3 | public interface IProduct
4 | {
5 | string InstrumentName { get; }
6 | }
7 | }
--------------------------------------------------------------------------------
/EngineTests/TestModelWithoutInterfaces/IProduct.cs:
--------------------------------------------------------------------------------
1 | namespace EngineTests.TestModelWithoutInterfaces
2 | {
3 | public interface IProduct
4 | {
5 | string InstrumentName { get; }
6 | }
7 | }
--------------------------------------------------------------------------------
/EngineTests/IBingo.cs:
--------------------------------------------------------------------------------
1 | namespace EngineTests
2 | {
3 | public interface IBingo
4 | {
5 | int X { get; set; }
6 | int Y { get; set; }
7 | string Message { get; set; }
8 | }
9 | }
--------------------------------------------------------------------------------
/EngineTests/TestModelWithInterfaces/ICdsTrade.cs:
--------------------------------------------------------------------------------
1 | namespace EngineTests.TestModelWithInterfaces
2 | {
3 | public interface ICdsTrade : ITrade
4 | {
5 | ICreditDefaultSwap CdsProduct { get; set; }
6 | }
7 | }
--------------------------------------------------------------------------------
/EngineTests/IAbcd.cs:
--------------------------------------------------------------------------------
1 | namespace EngineTests
2 | {
3 | public interface IAbcd
4 | {
5 | int A { get; set; }
6 | int B { get; set; }
7 | int C { get; set; }
8 | int D { get; set; }
9 | }
10 | }
--------------------------------------------------------------------------------
/EngineTests/Bingo.cs:
--------------------------------------------------------------------------------
1 | namespace EngineTests
2 | {
3 | public class Bingo : IBingo
4 | {
5 | public int X { get; set; }
6 |
7 | public int Y { get; set; }
8 |
9 | public string Message { get; set; }
10 | }
11 | }
--------------------------------------------------------------------------------
/EngineTests/Xyz.cs:
--------------------------------------------------------------------------------
1 | namespace EngineTests
2 | {
3 | public class Xyz
4 | {
5 | public virtual int X { get; set; }
6 | public virtual int Y { get; set; }
7 | public virtual int Z { get; set; }
8 |
9 | }
10 | }
--------------------------------------------------------------------------------
/EngineTests/Abcd.cs:
--------------------------------------------------------------------------------
1 | namespace EngineTests
2 | {
3 | public class Abcd : IAbcd
4 | {
5 | public int A { get; set; }
6 | public int B { get; set; }
7 | public int C { get; set; }
8 | public int D { get; set; }
9 | }
10 | }
--------------------------------------------------------------------------------
/EngineTests/TestModelWithoutInterfaces/CdsTrade.cs:
--------------------------------------------------------------------------------
1 | namespace EngineTests.TestModelWithoutInterfaces
2 | {
3 | public class CdsTrade : Trade
4 | {
5 | public CreditDefaultSwap CdsProduct
6 | {
7 | get => (CreditDefaultSwap) Product;
8 |
9 | set => Product = value;
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/EngineTests/TestModelWithInterfaces/CdsTrade2.cs:
--------------------------------------------------------------------------------
1 | namespace EngineTests.TestModelWithInterfaces
2 | {
3 | public class CdsTrade2 : Trade2
4 | {
5 | public virtual CreditDefaultSwap2 CdsProduct
6 | {
7 | get => (CreditDefaultSwap2) Product;
8 |
9 | set => Product = value;
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/EngineTests/Dog.cs:
--------------------------------------------------------------------------------
1 | namespace EngineTests
2 | {
3 | public class Dog
4 | {
5 | public bool IsAnimal { get; set; }
6 | public bool IsDangerous { get; set; }
7 |
8 | public string Name { get; set; }
9 |
10 | public int Age { get; set; }
11 |
12 | public string FavoriteToy { get; set; }
13 | }
14 | }
--------------------------------------------------------------------------------
/EngineTests/TestModelWithInterfaces/CdsTrade.cs:
--------------------------------------------------------------------------------
1 | namespace EngineTests.TestModelWithInterfaces
2 | {
3 | public class CdsTrade : Trade, ICdsTrade
4 | {
5 | public ICreditDefaultSwap CdsProduct
6 | {
7 | get { return (ICreditDefaultSwap) Product; }
8 |
9 | set { Product = value; }
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/.github/workflows/dotnetcore.yml:
--------------------------------------------------------------------------------
1 | name: .NET Core
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: Setup .NET Core
13 | uses: actions/setup-dotnet@v1
14 | with:
15 | dotnet-version: 3.1.100
16 | - name: Build with dotnet
17 | run: dotnet build --configuration Release
18 |
--------------------------------------------------------------------------------
/BusinessRulesEngine.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | True
--------------------------------------------------------------------------------
/RulesEngine/RulesEngine/Explain/ExplainExpression.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace RulesEngine.RulesEngine.Explain
3 | {
4 | ///
5 | /// Abstract base class for the queries
6 | ///
7 |
8 | public abstract class ExplainExpression
9 | {
10 | ///
11 | /// Check if the current query is valid. Validity rules are specific to each subclass
12 | ///
13 | public abstract bool IsValid { get; }
14 |
15 | }
16 | }
--------------------------------------------------------------------------------
/EngineTests/XyzRules.cs:
--------------------------------------------------------------------------------
1 | using RulesEngine.RulesEngine;
2 |
3 | namespace EngineTests
4 | {
5 | public class XyzRules : MappingRules
6 | {
7 | public XyzRules()
8 | {
9 | Set(x => x.Y)
10 | .With(x => x.X * 2)
11 | .OnChanged(x => x.X);
12 |
13 |
14 | Set(x => x.Z)
15 | .With(x => x.Y * 2)
16 | .OnChanged(x => x.Y);
17 |
18 |
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/EngineTests/EngineTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/EngineTests/TestModelWithInterfaces/ICreditDefaultSwap.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace EngineTests.TestModelWithInterfaces
4 | {
5 | public interface ICreditDefaultSwap : IProduct
6 | {
7 | DateTime? MaturityDate { get; set; }
8 |
9 | string RefEntity { get; set; }
10 |
11 | string Tenor { get; set; }
12 |
13 | decimal Spread { get; set; }
14 |
15 | decimal Nominal { get; set; }
16 |
17 | string Currency { get; set; }
18 | string Seniority { get; set; }
19 | string Restructuring { get; set; }
20 | string TransactionType { get; set; }
21 | }
22 | }
--------------------------------------------------------------------------------
/EngineTests/TestModelWithoutInterfaces/CreditDefaultSwap.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace EngineTests.TestModelWithoutInterfaces
4 | {
5 | public class CreditDefaultSwap : IProduct
6 | {
7 | public DateTime? MaturityDate { get; set; }
8 |
9 | public string RefEntity { get; set; }
10 |
11 | public string Tenor { get; set; }
12 |
13 | public decimal Spread { get; set; }
14 |
15 | public decimal Nominal { get; set; }
16 |
17 | public string Currency { get; set; }
18 |
19 | public string Seniority { get; set; }
20 |
21 | public string Restructuring { get; set; }
22 |
23 | public string TransactionType { get; set; }
24 | public string InstrumentName { get; private set; }
25 | }
26 | }
--------------------------------------------------------------------------------
/EngineTests/TestModelWithInterfaces/CreditDefaultSwap.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace EngineTests.TestModelWithInterfaces
4 | {
5 | public class CreditDefaultSwap : ICreditDefaultSwap
6 | {
7 | public string InstrumentName { get; private set; }
8 |
9 | public DateTime? MaturityDate { get; set; }
10 |
11 | public string RefEntity { get; set; }
12 |
13 | public string Tenor { get; set; }
14 |
15 | public decimal Spread { get; set; }
16 |
17 | public decimal Nominal { get; set; }
18 |
19 | public string Currency { get; set; }
20 |
21 | public string Seniority { get; set; }
22 |
23 | public string Restructuring { get; set; }
24 |
25 | public string TransactionType { get; set; }
26 | }
27 | }
--------------------------------------------------------------------------------
/EngineTests/TestModelWithInterfaces/ITrade.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace EngineTests.TestModelWithInterfaces
4 | {
5 | public interface ITrade
6 | {
7 | string CounterpartyRole { get; set; }
8 |
9 | DateTime? TradeDate { get; set; }
10 |
11 | DateTime? ValueDate { get; set; }
12 |
13 | string Counterparty { get; set; }
14 |
15 | string ContractId { get; set; }
16 |
17 | string Folder { get; set; }
18 |
19 | string BrokerParty { get; set; }
20 |
21 | string Trader { get; set; }
22 |
23 | string Sales { get; set; }
24 |
25 | string ClearingHouse { get; set; }
26 |
27 | bool MandatoryClearing { get; set; }
28 |
29 | IProduct Product { get; set; }
30 |
31 | decimal SalesCredit { get; set; }
32 | }
33 | }
--------------------------------------------------------------------------------
/RulesEngine/RulesEngine/Explain/NullQueryable.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Linq.Expressions;
3 | using Remotion.Linq;
4 | using Remotion.Linq.Parsing.Structure;
5 |
6 | namespace RulesEngine.RulesEngine.Explain
7 | {
8 | internal class NullQueryable : QueryableBase
9 | {
10 | public NullQueryable(IQueryParser queryParser, IQueryExecutor executor) : base(queryParser, executor)
11 | {
12 | }
13 |
14 | public NullQueryable(IQueryProvider provider) : base(provider)
15 | {
16 | }
17 |
18 | public NullQueryable(IQueryProvider provider, Expression expression) : base(provider, expression)
19 | {
20 | }
21 |
22 | public NullQueryable(IQueryExecutor executor) : base(QueryParser.CreateDefault(), executor)
23 | {
24 | }
25 |
26 |
27 | }
28 | }
--------------------------------------------------------------------------------
/EngineTests/AbcdRules.cs:
--------------------------------------------------------------------------------
1 | using RulesEngine.RulesEngine;
2 |
3 | namespace EngineTests
4 | {
5 | public class AbcdRules : MappingRules
6 | {
7 | public AbcdRules()
8 | {
9 | Set(x => x.B)
10 | .With(x => x.A)
11 | .If(x => x.A < 100)
12 | .OnChanged(x => x.A);
13 |
14 |
15 | Set(x => x.C)
16 | .With(x => x.B)
17 | .If(x => x.C < 100)
18 | .OnChanged(x => x.B);
19 |
20 |
21 | Set(x => x.D)
22 | .With(x => x.C)
23 | .If(x => x.D < 100)
24 | .OnChanged(x => x.C);
25 |
26 |
27 | Set(x => x.A)
28 | .With(x => x.D + 1)
29 | .If(x => x.A < 100)
30 | .OnChanged(x => x.D);
31 |
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/EngineTests/TestModelWithInterfaces/CreditDefaultSwap2.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace EngineTests.TestModelWithInterfaces
4 | {
5 | public class CreditDefaultSwap2:Product2
6 | {
7 | public virtual string InstrumentName
8 | {
9 | get => "CDS";
10 | }
11 |
12 | public virtual DateTime? MaturityDate { get; set; }
13 |
14 | public virtual string RefEntity { get; set; }
15 |
16 | public virtual string Tenor { get; set; }
17 |
18 | public virtual decimal Spread { get; set; }
19 |
20 | public virtual decimal Nominal { get; set; }
21 |
22 | public virtual string Currency { get; set; }
23 |
24 | public virtual string Seniority { get; set; }
25 |
26 | public virtual string Restructuring { get; set; }
27 |
28 | public virtual string TransactionType { get; set; }
29 | }
30 | }
--------------------------------------------------------------------------------
/EngineTests/TestModelWithoutInterfaces/Trade.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace EngineTests.TestModelWithoutInterfaces
4 | {
5 | public class Trade
6 | {
7 | public string CounterpartyRole { get; set; }
8 |
9 | public DateTime? TradeDate { get; set; }
10 |
11 | public DateTime? ValueDate { get; set; }
12 |
13 | public string Counterparty { get; set; }
14 |
15 | public string ContractId { get; set; }
16 |
17 | public string Folder { get; set; }
18 |
19 | public string BrokerParty { get; set; }
20 |
21 | public string Trader { get; set; }
22 |
23 | public string Sales { get; set; }
24 |
25 | public string ClearingHouse { get; set; }
26 |
27 | public bool MandatoryClearing { get; set; }
28 |
29 | public IProduct Product { get; set; }
30 |
31 | public decimal SalesCredit { get; set; }
32 | }
33 | }
--------------------------------------------------------------------------------
/EngineTests/TestModelWithInterfaces/Trade.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace EngineTests.TestModelWithInterfaces
4 | {
5 | public class Trade : ITrade
6 | {
7 | public string CounterpartyRole { get; set; }
8 |
9 |
10 | public DateTime? TradeDate { get; set; }
11 |
12 | public DateTime? ValueDate { get; set; }
13 |
14 | public string Counterparty { get; set; }
15 |
16 | public string ContractId { get; set; }
17 |
18 | public string Folder { get; set; }
19 |
20 | public string BrokerParty { get; set; }
21 |
22 | public string Trader { get; set; }
23 |
24 | public string Sales { get; set; }
25 |
26 | public string ClearingHouse { get; set; }
27 |
28 | public bool MandatoryClearing { get; set; }
29 |
30 | public IProduct Product { get; set; }
31 |
32 | public decimal SalesCredit { get; set; }
33 | }
34 | }
--------------------------------------------------------------------------------
/EngineTests/CompositeObjectsWithoutInterfacesTestFixture.cs:
--------------------------------------------------------------------------------
1 | using EngineTests.TestModelWithoutInterfaces;
2 | using NUnit.Framework;
3 | using RulesEngine.Interceptors;
4 |
5 | namespace EngineTests
6 | {
7 | [TestFixture]
8 | public class CompositeObjectsWithoutInterfacesTestFixture
9 | {
10 | [Test]
11 | public void Counterparty_change_fills_product()
12 | {
13 | var trade = new CdsTrade
14 | {
15 | Product = new CreditDefaultSwap()
16 | };
17 |
18 | dynamic p = new DynamicWrapper(trade, new CdsRules());
19 |
20 | p.CdsProduct.RefEntity = "AXA";
21 |
22 | p.Counterparty = "CHASEOTC";
23 |
24 | Assert.AreEqual("ICEURO", trade.ClearingHouse);
25 | Assert.AreEqual("MMR", trade.CdsProduct.Restructuring);
26 | Assert.AreEqual("SNR", trade.CdsProduct.Seniority);
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/RulesEngine/RulesEngine/Explain/NullExecutor.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using Remotion.Linq;
4 |
5 | namespace RulesEngine.RulesEngine.Explain
6 | {
7 | public class NullExecutor : IQueryExecutor
8 | {
9 |
10 |
11 | public ExplainOrExpression Expression { get; private set; }
12 |
13 | public T ExecuteScalar(QueryModel queryModel)
14 | {
15 | return default(T);
16 | }
17 |
18 | public T ExecuteSingle(QueryModel queryModel, bool returnDefaultWhenEmpty)
19 | {
20 | return default(T);
21 | }
22 |
23 | public IEnumerable ExecuteCollection(QueryModel queryModel)
24 | {
25 | var visitor = new QueryVisitor();
26 |
27 | visitor.VisitQueryModel(queryModel);
28 |
29 | Expression = visitor.RootExpression;
30 |
31 | return Enumerable.Empty();
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/EngineTests/TestModelWithInterfaces/Trade2.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace EngineTests.TestModelWithInterfaces
4 | {
5 | public class Trade2
6 | {
7 | public virtual string CounterpartyRole { get; set; }
8 |
9 |
10 | public virtual DateTime? TradeDate { get; set; }
11 |
12 | public virtual DateTime? ValueDate { get; set; }
13 |
14 | public virtual string Counterparty { get; set; }
15 |
16 | public virtual string ContractId { get; set; }
17 |
18 | public virtual string Folder { get; set; }
19 |
20 | public virtual string BrokerParty { get; set; }
21 |
22 | public virtual string Trader { get; set; }
23 |
24 | public virtual string Sales { get; set; }
25 |
26 | public virtual string ClearingHouse { get; set; }
27 |
28 | public virtual bool MandatoryClearing { get; set; }
29 |
30 | public virtual Product2 Product { get; set; }
31 |
32 | public virtual decimal SalesCredit { get; set; }
33 | }
34 | }
--------------------------------------------------------------------------------
/RulesEngine/RulesEngine/Explain/QueryOperator.cs:
--------------------------------------------------------------------------------
1 | namespace RulesEngine.RulesEngine.Explain
2 | {
3 | ///
4 | /// Operators to be used in
5 | /// On the server side these operators can be directly applied to indexes
6 | ///
7 | public enum QueryOperator
8 | {
9 | ///
10 | /// Equality operator(==)
11 | ///
12 | Eq,
13 |
14 | ///
15 | /// Greater
16 | ///
17 | Gt,
18 |
19 | ///
20 | /// Greater or Equal
21 | ///
22 | Ge,
23 |
24 | ///
25 | /// Less
26 | ///
27 | Lt,
28 |
29 | ///
30 | /// Less or equal
31 | ///
32 | Le,
33 |
34 |
35 | ///
36 | /// Applies to list keys
37 | ///
38 | In,
39 |
40 | NotEqual
41 | }
42 | }
--------------------------------------------------------------------------------
/EngineTests/DogRules.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using RulesEngine.RulesEngine;
3 |
4 | namespace EngineTests
5 | {
6 | public class DogRules : MappingRules
7 | {
8 | public DogRules()
9 | {
10 |
11 | Set(d => d.IsAnimal).With(d => true).OnChanged(d=>d.IsAnimal);
12 |
13 | Set(d=>d.Name).With(d=> "mr. " + d.Name ).If(x=> x.Name != "Clara" && !x.Name.StartsWith("mr.")).OnChanged(d=>d.Name);
14 |
15 | Set(d=>d.IsDangerous).With(d=>d.Age > 3 && d.Name != "Fluffy" ).OnChanged(d=>d.Age);
16 |
17 | Set(d=>d.FavoriteToy).With(d=>GetFavoriteToy(), "find him a toy").If(d=>d.FavoriteToy == null, "he does not already have one").OnChanged(d=>d.FavoriteToy);
18 |
19 | }
20 |
21 | static string GetFavoriteToy()
22 | {
23 | return "ball";
24 | }
25 |
26 | protected override void Trace(Rule triggeredRule, string triggerProperty, Dog instance)
27 | {
28 | Console.WriteLine(triggeredRule);
29 | Console.WriteLine($"triggered by {triggerProperty}");
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/EngineTests/BingoRules.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using RulesEngine.RulesEngine;
3 |
4 | namespace EngineTests
5 | {
6 | public class BingoRules : MappingRules
7 | {
8 | public BingoRules()
9 | {
10 | Set(i => i.X)
11 | .With(i => i.Y + 1)
12 | .If(i => i.X < 100)
13 | .OnChanged(i => i.Y);
14 |
15 |
16 | Set(i => i.Y)
17 | .With(i => i.X + 1)
18 | .If(i => i.Y < 100)
19 | .OnChanged(i => i.X);
20 |
21 |
22 | Set(i => i.Message)
23 | .With(i => "BINGO")
24 | .If(i => i.X >= 100 || i.Y >= 100)
25 | .OnChanged(i => i.X, i=>i.Y);
26 |
27 | }
28 |
29 | #region Overrides of MappingRules
30 |
31 | protected override void Trace(Rule triggeredRule, string triggerProperty, IBingo instance)
32 | {
33 | Console.WriteLine("{0, 10} : {1}", triggerProperty, triggeredRule);
34 | }
35 |
36 | #endregion
37 | }
38 | }
--------------------------------------------------------------------------------
/RulesEngine/RulesEngine.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;netstandard2.1
5 | true
6 | BusinessRules.Engine
7 | 1.0.14
8 | Dan Ionescu
9 | Usinesoft
10 | A rules engine for .Net applications
11 | Copyright USINESOFT 2015
12 | Apache-2.0
13 | https://github.com/usinesoft/BusinessRulesEngine
14 | business rules engine enrichment financial
15 | Provide optional explanation for rules. Multi-target support
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/RulesEngine/RulesEngine/Explain/ExplainOrExpression.cs:
--------------------------------------------------------------------------------
1 | #region
2 |
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | #endregion
8 |
9 | namespace RulesEngine.RulesEngine.Explain
10 | {
11 | ///
12 | /// A list of and queries bound by an OR operator
13 | ///
14 |
15 | public class ExplainOrExpression : ExplainExpression
16 | {
17 | private readonly List _elements = new List();
18 |
19 | public override bool IsValid
20 | {
21 | get { return Elements.All(element => element.IsValid); }
22 | }
23 |
24 | ///
25 | /// The elements of type
26 | ///
27 | public IList Elements => _elements;
28 |
29 |
30 | public bool MultipleWhereClauses { get; set; }
31 |
32 |
33 | public override string ToString()
34 | {
35 | if (_elements.Count == 0)
36 | return "";
37 |
38 | var sb = new StringBuilder();
39 | sb.Append(string.Join(" OR ",_elements.Select(e => e.ToString()).ToArray()));
40 |
41 | return sb.ToString();
42 | }
43 |
44 | }
45 | }
--------------------------------------------------------------------------------
/RulesEngine/RulesEngine/Explain/ExplainAndExpression.cs:
--------------------------------------------------------------------------------
1 | #region
2 |
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | #endregion
8 |
9 | namespace RulesEngine.RulesEngine.Explain
10 | {
11 | ///
12 | /// A list of atomic queries bound by an AND operator
13 | ///
14 | public class ExplainAndExpression : ExplainExpression
15 | {
16 | ///
17 | /// Create an empty query (called internally by the query builder)
18 | ///
19 | public ExplainAndExpression()
20 | {
21 | Elements = new List();
22 | }
23 |
24 | ///
25 | /// The contained atomic queries should apply to different keys
26 | ///
27 | public override bool IsValid
28 | {
29 | get { return Elements.All(atomicQuery => atomicQuery.IsValid); }
30 | }
31 |
32 | ///
33 | /// Accessor for the underlying elements (
34 | ///
35 | public List Elements { get; private set; }
36 |
37 |
38 | public override string ToString()
39 | {
40 | if (Elements.Count == 0)
41 | return "";
42 | if (Elements.Count == 1)
43 | return Elements[0].ToString();
44 |
45 | var sb = new StringBuilder();
46 | sb.Append(string.Join(" AND ", Elements.Select(e => e.ToString()).ToArray()));
47 |
48 | return sb.ToString();
49 | }
50 |
51 |
52 | }
53 | }
--------------------------------------------------------------------------------
/EngineTests/DynamicWrapperTestFixture.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.ComponentModel;
3 | using NUnit.Framework;
4 | using RulesEngine.Interceptors;
5 |
6 | namespace EngineTests
7 | {
8 | [TestFixture]
9 | public class DynamicWrapperTestFixture
10 | {
11 | [Test]
12 | public void Intercept_changes_with_dynamic_wrapper()
13 | {
14 | {
15 | var instance = new Abcd();
16 |
17 | dynamic abcd = new DynamicWrapper(instance, new AbcdRules());
18 |
19 | var inotify = (INotifyPropertyChanged) abcd;
20 |
21 | var changed = new List();
22 |
23 | inotify.PropertyChanged += (sender, args) => changed.Add(args.PropertyName);
24 |
25 | abcd.A = 1;
26 |
27 | Assert.AreEqual(100, abcd.A);
28 | Assert.AreEqual(100, instance.A);
29 | Assert.AreEqual(4, changed.Count);
30 | }
31 |
32 | {
33 | var instance = new Bingo();
34 |
35 | dynamic bingo = new DynamicWrapper(instance, new BingoRules());
36 |
37 | var inotify = (INotifyPropertyChanged) bingo;
38 |
39 | var changed = new List();
40 |
41 | inotify.PropertyChanged += (sender, args) => changed.Add(args.PropertyName);
42 |
43 | bingo.X = 1;
44 |
45 | Assert.AreEqual("BINGO", bingo.Message);
46 | Assert.AreEqual(101, instance.X);
47 | Assert.AreEqual(3, changed.Count);
48 |
49 | bingo.Message = "BONGO";
50 | Assert.AreEqual("BONGO", bingo.Message);
51 | }
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/EngineTests/CompositeObjectsWithInterfacesTestFixture.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using EngineTests.TestModelWithInterfaces;
3 | using NUnit.Framework;
4 | using RulesEngine.Interceptors;
5 |
6 | namespace EngineTests
7 | {
8 | [TestFixture]
9 | public class CompositeObjectsWithInterfacesTestFixture
10 | {
11 |
12 | [Test]
13 | public void Multi_threaded_test()
14 | {
15 | Parallel.For(0, 1000, (i) => Counterparty_change_fills_product());
16 | }
17 |
18 | [Test]
19 | public void Counterparty_change_fills_product()
20 | {
21 | var trade = new CdsTrade
22 | {
23 | Product = new CreditDefaultSwap()
24 | };
25 |
26 | var p = new InterfaceWrapper(trade, new CdsRules()).Target;
27 |
28 | p.CdsProduct.RefEntity = "AXA";
29 |
30 | p.Counterparty = "CHASEOTC";
31 |
32 | Assert.AreEqual("ICEURO", trade.ClearingHouse);
33 | Assert.AreEqual("MMR", trade.CdsProduct.Restructuring);
34 | Assert.AreEqual("SNR", trade.CdsProduct.Seniority);
35 |
36 | }
37 |
38 | [Test]
39 | public void Counterparty_change_fills_product2()
40 | {
41 | var trade = new CdsTrade2
42 | {
43 | Product = new CreditDefaultSwap2()
44 | };
45 |
46 | var p = new InterfaceWrapper(trade, new CdsRules2()).Target;
47 |
48 | p.CdsProduct.RefEntity = "AXA";
49 |
50 | p.Counterparty = "CHASEOTC";
51 |
52 | Assert.AreEqual("ICEURO", trade.ClearingHouse);
53 | Assert.AreEqual("MMR", trade.CdsProduct.Restructuring);
54 | Assert.AreEqual("SNR", trade.CdsProduct.Seniority);
55 |
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/RulesEngine/RulesEngine/Rule.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace RulesEngine.RulesEngine
6 | {
7 | ///
8 | /// A rule assigns a value to a single property. It can be triggered by a list of properties
9 | ///
10 | ///
11 | public class Rule
12 | {
13 | public ISet TriggerProperties { get; set; } = new HashSet();
14 |
15 |
16 | ///
17 | /// Name of the property that will be updated by this rule
18 | ///
19 | public string TargetPropertyName { get; set; }
20 |
21 | ///
22 | /// Compiled method which updates the target property. It return true if value changed
23 | ///
24 | public Func Updater { get; set; }
25 |
26 | public string IfExplained { get; set; }
27 |
28 | public string ValueComputerExplained { get; set; }
29 |
30 | #region Overrides of Object
31 |
32 | ///
33 | /// Returns a string that represents the current object.
34 | ///
35 | ///
36 | /// A string that represents the current object.
37 | ///
38 | public override string ToString()
39 | {
40 | var builder = new StringBuilder();
41 |
42 | builder.Append(TargetPropertyName);
43 | builder.Append(" = ");
44 | builder.Append(ValueComputerExplained);
45 |
46 | if (!string.IsNullOrWhiteSpace(IfExplained))
47 | {
48 | builder.Append("\tIF ");
49 | builder.Append(IfExplained);
50 |
51 | }
52 |
53 | return builder.ToString();
54 |
55 |
56 | }
57 |
58 | #endregion
59 | }
60 | }
--------------------------------------------------------------------------------
/BusinessRulesEngine.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29418.71
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41024DC3-013A-4F4D-9C95-62D9BFA3D21E}"
7 | ProjectSection(SolutionItems) = preProject
8 | README.md = README.md
9 | EndProjectSection
10 | EndProject
11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RulesEngine", "RulesEngine\RulesEngine.csproj", "{D45E8F2E-F206-40B5-B15C-4D2D558969CF}"
12 | EndProject
13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EngineTests", "EngineTests\EngineTests.csproj", "{608454EB-29E7-4CE5-A829-F5B5B1A99FBB}"
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|Any CPU = Debug|Any CPU
18 | Release|Any CPU = Release|Any CPU
19 | EndGlobalSection
20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
21 | {D45E8F2E-F206-40B5-B15C-4D2D558969CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {D45E8F2E-F206-40B5-B15C-4D2D558969CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {D45E8F2E-F206-40B5-B15C-4D2D558969CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {D45E8F2E-F206-40B5-B15C-4D2D558969CF}.Release|Any CPU.Build.0 = Release|Any CPU
25 | {608454EB-29E7-4CE5-A829-F5B5B1A99FBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {608454EB-29E7-4CE5-A829-F5B5B1A99FBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {608454EB-29E7-4CE5-A829-F5B5B1A99FBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {608454EB-29E7-4CE5-A829-F5B5B1A99FBB}.Release|Any CPU.Build.0 = Release|Any CPU
29 | EndGlobalSection
30 | GlobalSection(SolutionProperties) = preSolution
31 | HideSolutionNode = FALSE
32 | EndGlobalSection
33 | GlobalSection(ExtensibilityGlobals) = postSolution
34 | SolutionGuid = {5671079C-E4FD-4953-9020-2F6DD442B523}
35 | EndGlobalSection
36 | EndGlobal
37 |
--------------------------------------------------------------------------------
/RulesEngine/Tools/RuntimeCompiler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Linq.Expressions;
5 |
6 | namespace RulesEngine.Tools
7 | {
8 | ///
9 | /// Caches compiled versions of expression trees
10 | ///
11 | ///
12 | ///
13 | public static class RuntimeCompiler
14 | {
15 | private static readonly Dictionary> PrecompiledActions =
16 | new Dictionary>();
17 |
18 | private static readonly Dictionary> PrecompiledGetters =
19 | new Dictionary>();
20 |
21 | public static Action SetterFromGetter(
22 | Expression> expression)
23 | {
24 | var propertyName = ExpressionTreeHelper.FullPropertyName(expression);
25 |
26 | lock (PrecompiledActions)
27 | {
28 | if (PrecompiledActions.TryGetValue(propertyName, out var setter))
29 | {
30 | return setter;
31 | }
32 |
33 | var valueParameterExpression = Expression.Parameter(typeof (TProperty));
34 | var targetExpression = expression.Body is UnaryExpression unaryExpression
35 | ? unaryExpression.Operand
36 | : expression.Body;
37 |
38 | var assign = Expression.Lambda>(
39 | Expression.Assign(targetExpression, Expression.Convert(valueParameterExpression, targetExpression.Type)),
40 | expression.Parameters.Single(), valueParameterExpression
41 | );
42 |
43 | return PrecompiledActions[propertyName] = assign.Compile();
44 | }
45 | }
46 |
47 | public static Func Getter(Expression> expression)
48 | {
49 | var propertyName = ExpressionTreeHelper.FullPropertyName(expression);
50 |
51 | lock (PrecompiledGetters)
52 | {
53 | if (PrecompiledGetters.TryGetValue(propertyName, out var getter))
54 | {
55 | return getter;
56 | }
57 |
58 | return PrecompiledGetters[propertyName] = expression.Compile();
59 | }
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/RulesEngine/Tools/ExpressionTreeHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq.Expressions;
4 | using System.Text;
5 |
6 | namespace RulesEngine.Tools
7 | {
8 | ///
9 | /// Common expression tree manipulation logic
10 | ///
11 | public static class ExpressionTreeHelper
12 | {
13 | ///
14 | /// Get the name of the most specific property expressed as an expression tree
15 | /// For example t=>t.Product.PremiumLeg.Coupon return "Coupon"
16 | ///
17 | ///
18 | ///
19 | ///
20 | ///
21 | public static string PropertyName(Expression> propertySelector)
22 | {
23 | if (propertySelector == null) throw new ArgumentNullException(nameof(propertySelector));
24 |
25 | if (propertySelector.Body.NodeType == ExpressionType.Convert)
26 | {
27 | if (propertySelector.Body is UnaryExpression convert)
28 | if (convert.Operand is MemberExpression memberExpression)
29 | return memberExpression.Member.Name;
30 | }
31 | else
32 | {
33 | if (propertySelector.Body is MemberExpression memberExpression) return memberExpression.Member.Name;
34 | }
35 |
36 | throw new ArgumentException("propertySelector must be a MemberExpression.", nameof(propertySelector));
37 | }
38 |
39 | ///
40 | /// Get the full name of the most specific property expressed as an expression tree
41 | /// For example t=>t.Product.PremiumLeg.Coupon return "Product.PremiumLeg.Coupon"
42 | ///
43 | ///
44 | ///
45 | ///
46 | ///
47 | public static string FullPropertyName(Expression> propertySelector)
48 | {
49 | if (propertySelector == null) throw new ArgumentNullException(nameof(propertySelector));
50 |
51 | var memberExpression = propertySelector.Body as MemberExpression;
52 |
53 | if (memberExpression == null)
54 | throw new ArgumentException("propertySelector must be a MemberExpression.", nameof(propertySelector));
55 |
56 | var components = new List
57 | {
58 | memberExpression.Member.Name
59 | };
60 | var inner = memberExpression.Expression as MemberExpression;
61 | while (inner != null)
62 | {
63 | components.Add(inner.Member.Name);
64 | inner = inner.Expression as MemberExpression;
65 | }
66 |
67 | var sb = new StringBuilder();
68 | components.Reverse();
69 |
70 | foreach (var component in components)
71 | sb.Append(component)
72 | .Append(".");
73 |
74 | return sb.ToString()
75 | .TrimEnd('.');
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/EngineTests/InterfaceWrapperTestFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Diagnostics;
5 | using NUnit.Framework;
6 | using RulesEngine.Interceptors;
7 |
8 | namespace EngineTests
9 | {
10 | [TestFixture]
11 | public class InterfaceWrapperTestFixture
12 | {
13 | [Test]
14 | public void Intercept_changes_with_interface_wrapper()
15 | {
16 | {
17 | var instance = new Abcd();
18 |
19 | var rules = new AbcdRules();
20 |
21 | Assert.AreEqual(4, rules.RulesCount);
22 |
23 | var abcd = new InterfaceWrapper(instance, rules);
24 |
25 | var inotify = (INotifyPropertyChanged) abcd;
26 |
27 | var changed = new List();
28 |
29 | inotify.PropertyChanged += (sender, args) => changed.Add(args.PropertyName);
30 |
31 | abcd.Target.A = 1;
32 |
33 | Assert.AreEqual(100, abcd.Target.A);
34 | Assert.AreEqual(100, instance.A);
35 | Assert.AreEqual(4, changed.Count);
36 | }
37 |
38 | {
39 | var instance = new Bingo();
40 |
41 | var bingo = new InterfaceWrapper(instance, new BingoRules());
42 |
43 | var inotify = (INotifyPropertyChanged) bingo;
44 |
45 | var changed = new List();
46 |
47 | inotify.PropertyChanged += (sender, args) => changed.Add(args.PropertyName);
48 |
49 | bingo.Target.X = 1;
50 |
51 | Assert.AreEqual("BINGO", bingo.Target.Message);
52 | Assert.AreEqual(101, instance.X);
53 | Assert.AreEqual(3, changed.Count);
54 |
55 | bingo.Target.Message = "BONGO";
56 | Assert.AreEqual("BONGO", bingo.Target.Message);
57 | }
58 | }
59 |
60 |
61 | [Test]
62 | public void Intercept_changes_with_interface_wrapper_without_interface()
63 | {
64 | {
65 | var instance = new Xyz();
66 |
67 | var xyz = new InterfaceWrapper(instance, new XyzRules());
68 |
69 | xyz.Target.X = 1;
70 |
71 | Assert.AreEqual(2, instance.Y);
72 |
73 | Assert.AreEqual(4, instance.Z);
74 |
75 | }
76 | }
77 |
78 |
79 | [Test]
80 | public void Performance_test()
81 | {
82 | var rules = new XyzRules();
83 |
84 | // warm up
85 | {
86 | var instance = new Xyz();
87 |
88 | var xyz = new InterfaceWrapper(instance, rules);
89 |
90 | xyz.Target.X = 1;
91 |
92 | }
93 |
94 | var sw = new Stopwatch();
95 |
96 | sw.Start();
97 | for (int i = 0; i < 1000; i++)
98 | {
99 | var instance = new Xyz();
100 |
101 | var xyz = new InterfaceWrapper(instance, rules);
102 |
103 | xyz.Target.X = 1;
104 |
105 | }
106 |
107 | sw.Stop();
108 |
109 | Console.WriteLine($"took {sw.ElapsedMilliseconds} ms");
110 | }
111 | }
112 |
113 | }
--------------------------------------------------------------------------------
/EngineTests/TestModelWithoutInterfaces/CdsRules.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using RulesEngine.RulesEngine;
3 |
4 | namespace EngineTests.TestModelWithoutInterfaces
5 | {
6 | public class CdsRules : MappingRules
7 | {
8 | public CdsRules()
9 | {
10 | Set(t => t.CounterpartyRole)
11 | .With(t => t.Sales != null ? "Client" : "Dealer")
12 | .OnChanged(t => t.Sales);
13 |
14 |
15 | Set(t => t.ClearingHouse)
16 | .With(t => GetDefaultClearingHouse(t.Counterparty, t.CdsProduct.RefEntity))
17 | .OnChanged(t => t.CdsProduct.RefEntity, t=>t.Counterparty);
18 |
19 |
20 | Set(t => t.SalesCredit)
21 | .With(t => Calculator(t.CdsProduct.Spread, t.CdsProduct.Nominal))
22 | .OnChanged(t => t.CdsProduct.Spread, t=> t.CdsProduct);
23 |
24 | Set(t => t.CdsProduct.TransactionType)
25 | .With(t => GetTransactionType(t.CdsProduct.RefEntity))
26 | .OnChanged(t => t.CdsProduct.RefEntity);
27 |
28 |
29 | Set(t => t.CdsProduct.Currency)
30 | .With(t => GetDefaultCurrency(t.CdsProduct.TransactionType))
31 | .OnChanged(t => t.CdsProduct.TransactionType);
32 |
33 |
34 | Set(t => t.CdsProduct.Restructuring)
35 | .With(t => GetDefaultRestructuring(t.CdsProduct.TransactionType))
36 | .OnChanged(t => t.CdsProduct.TransactionType);
37 |
38 |
39 | Set(t => t.CdsProduct.Seniority)
40 | .With(t => GetDefaultSeniority(t.CdsProduct.TransactionType))
41 | .OnChanged(t => t.CdsProduct.TransactionType);
42 |
43 | }
44 |
45 | private string GetTransactionType(string refEntity)
46 | {
47 | if (refEntity == "AXA")
48 | {
49 | return "Standard European Corporate";
50 | }
51 |
52 | return null;
53 | }
54 |
55 | private string GetDefaultSeniority(string transactionType)
56 | {
57 | if (transactionType == "Standard European Corporate")
58 | {
59 | return "SNR";
60 | }
61 |
62 | return null;
63 | }
64 |
65 | private string GetDefaultRestructuring(string transactionType)
66 | {
67 | if (transactionType == "Standard European Corporate")
68 | {
69 | return "MMR";
70 | }
71 |
72 | return null;
73 | }
74 |
75 | private string GetDefaultCurrency(string transactionType)
76 | {
77 | if (transactionType == "Standard European Corporate")
78 | {
79 | return "EUR";
80 | }
81 |
82 | return null;
83 | }
84 |
85 | private static decimal Calculator(decimal spread, decimal nominal)
86 | {
87 | return 4;
88 | }
89 |
90 | private static string GetDefaultClearingHouse(string counterpary, string refEntity)
91 | {
92 | if (refEntity == "AXA" && counterpary == "CHASEOTC")
93 | {
94 | return "ICEURO";
95 | }
96 |
97 | if (refEntity == "RENAULT" && counterpary == "CHASEOTC")
98 | {
99 | return "ICETRUST";
100 | }
101 |
102 | return null;
103 | }
104 |
105 |
106 | protected override void Trace(Rule triggeredRule, string triggerProperty, CdsTrade instance)
107 | {
108 | Console.WriteLine(triggeredRule);
109 | }
110 | }
111 | }
--------------------------------------------------------------------------------
/EngineTests/TestModelWithInterfaces/CdsRules2.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using RulesEngine.RulesEngine;
3 |
4 | namespace EngineTests.TestModelWithInterfaces
5 | {
6 | public class CdsRules2 : MappingRules
7 | {
8 | public CdsRules2()
9 | {
10 | Set(t => t.CounterpartyRole)
11 | .With(t => t.Sales != null ? "Client" : "Dealer")
12 | .OnChanged(t => t.Sales);
13 |
14 |
15 | Set(t => t.ClearingHouse)
16 | .With(t => GetDefaultClearingHouse(t.Counterparty, t.CdsProduct.RefEntity))
17 | .OnChanged(t => t.CdsProduct.RefEntity, t => t.Counterparty);
18 |
19 |
20 | Set(t => t.SalesCredit)
21 | .With(t => Calculator(t.CdsProduct.Spread, t.CdsProduct.Nominal))
22 | .OnChanged(t => t.CdsProduct.Spread, t => t.CdsProduct.RefEntity);
23 |
24 |
25 | Set(t => t.CdsProduct.TransactionType)
26 | .With(t => GetTransactionType(t.CdsProduct.RefEntity))
27 | .OnChanged(t => t.CdsProduct.RefEntity);
28 |
29 |
30 | Set(t => t.CdsProduct.Currency)
31 | .With(t => GetDefaultCurrency(t.CdsProduct.TransactionType))
32 | .OnChanged(t => t.CdsProduct.TransactionType);
33 |
34 |
35 | Set(t => t.CdsProduct.Restructuring)
36 | .With(t => GetDefaultRestructuring(t.CdsProduct.TransactionType))
37 | .OnChanged(t => t.CdsProduct.TransactionType);
38 |
39 |
40 | Set(t => t.CdsProduct.Seniority)
41 | .With(t => GetDefaultSeniority(t.CdsProduct.TransactionType))
42 | .OnChanged(t => t.CdsProduct.TransactionType);
43 |
44 | }
45 |
46 | private string GetTransactionType(string refEntity)
47 | {
48 | if (refEntity == "AXA")
49 | {
50 | return "Standard European Corporate";
51 | }
52 |
53 | return null;
54 | }
55 |
56 | private string GetDefaultSeniority(string transactionType)
57 | {
58 | if (transactionType == "Standard European Corporate")
59 | {
60 | return "SNR";
61 | }
62 |
63 | return null;
64 | }
65 |
66 | private string GetDefaultRestructuring(string transactionType)
67 | {
68 | if (transactionType == "Standard European Corporate")
69 | {
70 | return "MMR";
71 | }
72 |
73 | return null;
74 | }
75 |
76 | private string GetDefaultCurrency(string transactionType)
77 | {
78 | if (transactionType == "Standard European Corporate")
79 | {
80 | return "EUR";
81 | }
82 |
83 | return null;
84 | }
85 |
86 | private static decimal Calculator(decimal spread, decimal nominal)
87 | {
88 | return 4;
89 | }
90 |
91 | private static string GetDefaultClearingHouse(string counterpary, string refEntity)
92 | {
93 | if (refEntity == "AXA" && counterpary == "CHASEOTC")
94 | {
95 | return "ICEURO";
96 | }
97 |
98 | if (refEntity == "RENAULT" && counterpary == "CHASEOTC")
99 | {
100 | return "ICETRUST";
101 | }
102 |
103 | return null;
104 | }
105 |
106 | #region Overrides of MappingRules
107 |
108 | protected override void Trace(Rule triggeredRule, string triggerProperty, CdsTrade2 instance)
109 | {
110 | Console.WriteLine(triggeredRule);
111 | }
112 |
113 | #endregion
114 | }
115 | }
--------------------------------------------------------------------------------
/RulesEngine/Interceptors/InterfaceWrapper.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using Castle.DynamicProxy;
3 | using RulesEngine.RulesEngine;
4 |
5 | namespace RulesEngine.Interceptors
6 | {
7 | ///
8 | /// Intercept calls to non sealed classes that have all public properties virtual
9 | /// Calls to setters are diverted to rule engine
10 | /// Calls to getters are intercepted to wrap the returned value (if it is an interface)
11 | /// Calls to methods are silently ignored
12 | ///
13 | ///
14 | public sealed class InterfaceWrapper : INotifyPropertyChanged
15 | where T : class
16 | {
17 | // ReSharper disable once StaticMemberInGenericType
18 | private static ProxyGenerator Generator { get; } = new ProxyGenerator();
19 |
20 | ///
21 | /// Initializes a new instance of the class.
22 | ///
23 | public InterfaceWrapper(T instance, MappingRules rules)
24 | {
25 | Target = typeof(T).IsInterface ? Generator.CreateInterfaceProxyWithTarget(instance, new Interceptor(rules, this, instance)) : Generator.CreateClassProxyWithTarget(instance, new Interceptor(rules, this, instance));
26 | }
27 |
28 | public T Target { get; }
29 |
30 | #region Implementation of INotifyPropertyChanged
31 |
32 | public event PropertyChangedEventHandler PropertyChanged;
33 |
34 | #endregion
35 |
36 | private void OnPropertyChanged(string propertyName = null)
37 | {
38 | var handler = PropertyChanged;
39 | handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
40 | }
41 |
42 | private class Interceptor : IInterceptor
43 | {
44 | private readonly InterfaceWrapper _parent;
45 | private readonly T _instance;
46 | private readonly MappingRules _rules;
47 |
48 | public Interceptor(MappingRules rules, InterfaceWrapper parent, T instance)
49 | {
50 | _rules = rules;
51 | _parent = parent;
52 | _instance = instance;
53 | }
54 |
55 | public void Intercept(IInvocation invocation)
56 | {
57 | var methodName = invocation.Method.Name;
58 | if (methodName.StartsWith("get_"))
59 | {
60 | invocation.Proceed();
61 |
62 | var getterReturnType = invocation.MethodInvocationTarget.ReturnType;
63 |
64 | if (getterReturnType.IsInterface)
65 | {
66 | // wrap the result of the getter in a proxy
67 | var proxy = Generator.CreateInterfaceProxyWithTarget(getterReturnType, invocation.ReturnValue,
68 | new Interceptor(_rules, _parent, _instance));
69 |
70 | invocation.ReturnValue = proxy;
71 | }
72 | else if(getterReturnType.IsClass && !getterReturnType.Namespace.StartsWith("System")) // avoid elementary types that are class
73 | {
74 | var proxy = Generator.CreateClassProxyWithTarget(getterReturnType, invocation.ReturnValue,
75 | new Interceptor(_rules, _parent, _instance));
76 |
77 | invocation.ReturnValue = proxy;
78 | }
79 | }
80 | else if (methodName.StartsWith("set_"))
81 | {
82 | var propertyName = methodName.Substring(4);
83 |
84 | var modified = _rules.SetProperty(propertyName, _instance, invocation.InvocationTarget, invocation.Arguments[0]);
85 |
86 | foreach (var property in modified)
87 | {
88 | _parent.OnPropertyChanged(property);
89 | }
90 | }
91 | }
92 | }
93 | }
94 | }
--------------------------------------------------------------------------------
/RulesEngine/RulesEngine/Explain/Explain.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Linq.Expressions;
4 | using System.Runtime.InteropServices;
5 | using Remotion.Linq;
6 | using Remotion.Linq.Clauses;
7 |
8 | namespace RulesEngine.RulesEngine.Explain
9 | {
10 | public static class Explain
11 | {
12 | public static string TryExplain(this Expression> ifExpression)
13 | {
14 | //create a fake queryable to force query parsing and capture resolution
15 |
16 | var executor = new NullExecutor();
17 | var queryable = new NullQueryable(executor);
18 |
19 | try
20 | {
21 | var unused = queryable.Where(ifExpression).ToList();
22 |
23 | return executor.Expression.ToString();
24 | }
25 | catch
26 | {
27 | // expression too complex
28 | return null;
29 | }
30 |
31 | }
32 |
33 | public static string TryExplain(this Expression> expression)
34 | {
35 | // create a fake queryable to force query parsing and capture resolution
36 |
37 | return ExplainSimple(expression.Body) ?? ExplainBinaryExpression(expression.Body) ?? "?";
38 |
39 | }
40 |
41 | private static string ExplainSimple(Expression expression)
42 | {
43 | if (expression is MemberExpression member)
44 | {
45 | return member.Member.Name;
46 | }
47 |
48 | if (expression is MethodCallExpression call)
49 | {
50 | return call.Method.Name + "()";
51 | }
52 |
53 | if (expression is ConstantExpression constant)
54 | {
55 | return constant.Value.ToString();
56 | }
57 |
58 | return null;
59 | }
60 |
61 |
62 |
63 | private static string ExplainBinaryExpression(Expression expr)
64 | {
65 | if (expr is BinaryExpression expression)
66 | {
67 | string left = ExplainSimple(expression.Left) ?? ExplainBinaryExpression(expression.Left) ??"?";
68 | string right = ExplainSimple(expression.Right) ?? ExplainBinaryExpression(expression.Right) ?? "?";
69 |
70 | if (expression.NodeType == ExpressionType.Add)
71 | return left + " + " +right;
72 |
73 | if (expression.NodeType == ExpressionType.Subtract)
74 | return left + " - " + right;
75 |
76 | if (expression.NodeType == ExpressionType.Multiply)
77 | return left + " * " + right;
78 |
79 | if (expression.NodeType == ExpressionType.Divide)
80 | return left + " / " + right;
81 |
82 | if (expression.NodeType == ExpressionType.LessThan)
83 | return left + " < " + right;
84 |
85 | if (expression.NodeType == ExpressionType.LessThanOrEqual)
86 | return left + " <= " + right;
87 |
88 | if (expression.NodeType == ExpressionType.GreaterThan)
89 | return left + " > " + right;
90 |
91 | if (expression.NodeType == ExpressionType.GreaterThanOrEqual)
92 | return left + " >= " + right;
93 |
94 | if (expression.NodeType == ExpressionType.Equal)
95 | return left + " = " + right;
96 |
97 | if (expression.NodeType == ExpressionType.NotEqual)
98 | return left + " <> " + right;
99 |
100 | if (expression.NodeType == ExpressionType.AndAlso)
101 | return left + " AND " + right;
102 |
103 | if (expression.NodeType == ExpressionType.Or)
104 | return left + " OR " + right;
105 |
106 |
107 | }
108 |
109 | return null;
110 | }
111 |
112 | }
113 | }
--------------------------------------------------------------------------------
/EngineTests/TestModelWithInterfaces/CdsRules.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using RulesEngine.RulesEngine;
3 |
4 | namespace EngineTests.TestModelWithInterfaces
5 | {
6 | public class CdsRules : MappingRules
7 | {
8 | public CdsRules()
9 | {
10 |
11 |
12 | Set(t => t.CounterpartyRole)
13 | .With(t => "Client")
14 | .If(t=>t.Sales != null)
15 | .OnChanged(t => t.Sales);
16 |
17 | Set(t => t.CounterpartyRole)
18 | .With(t => "Dealer")
19 | .If(t => t.Sales == null)
20 | .OnChanged(t => t.Sales);
21 |
22 |
23 | Set(t => t.ClearingHouse)
24 | .With(t => GetDefaultClearingHouse(t.Counterparty, t.CdsProduct.RefEntity))
25 | .OnChanged(t => t.CdsProduct.RefEntity, t => t.Counterparty);
26 |
27 |
28 | Set(t => t.SalesCredit)
29 | .With(t => Calculator(t.CdsProduct.Spread, t.CdsProduct.Nominal))
30 | .OnChanged(t => t.CdsProduct.Spread, t => t.CdsProduct.RefEntity);
31 |
32 |
33 | Set(t => t.CdsProduct.TransactionType)
34 | .With(t => GetTransactionType(t.CdsProduct.RefEntity))
35 | .OnChanged(t => t.CdsProduct.RefEntity);
36 |
37 |
38 | Set(t => t.CdsProduct.Currency)
39 | .With(t => GetDefaultCurrency(t.CdsProduct.TransactionType))
40 | .OnChanged(t => t.CdsProduct.TransactionType);
41 |
42 |
43 | Set(t => t.CdsProduct.Restructuring)
44 | .With(t => GetDefaultRestructuring(t.CdsProduct.TransactionType))
45 | .OnChanged(t => t.CdsProduct.TransactionType);
46 |
47 |
48 | Set(t => t.CdsProduct.Seniority)
49 | .With(t => GetDefaultSeniority(t.CdsProduct.TransactionType))
50 | .OnChanged(t => t.CdsProduct.TransactionType);
51 |
52 | }
53 |
54 | private string GetTransactionType(string refEntity)
55 | {
56 | if (refEntity == "AXA")
57 | {
58 | return "Standard European Corporate";
59 | }
60 |
61 | return null;
62 | }
63 |
64 | private string GetDefaultSeniority(string transactionType)
65 | {
66 | if (transactionType == "Standard European Corporate")
67 | {
68 | return "SNR";
69 | }
70 |
71 | return null;
72 | }
73 |
74 | private string GetDefaultRestructuring(string transactionType)
75 | {
76 | if (transactionType == "Standard European Corporate")
77 | {
78 | return "MMR";
79 | }
80 |
81 | return null;
82 | }
83 |
84 | private string GetDefaultCurrency(string transactionType)
85 | {
86 | if (transactionType == "Standard European Corporate")
87 | {
88 | return "EUR";
89 | }
90 |
91 | return null;
92 | }
93 |
94 | private static decimal Calculator(decimal spread, decimal nominal)
95 | {
96 | return 4;
97 | }
98 |
99 | private static string GetDefaultClearingHouse(string counterpary, string refEntity)
100 | {
101 | if (refEntity == "AXA" && counterpary == "CHASEOTC")
102 | {
103 | return "ICEURO";
104 | }
105 |
106 | if (refEntity == "RENAULT" && counterpary == "CHASEOTC")
107 | {
108 | return "ICETRUST";
109 | }
110 |
111 | return null;
112 | }
113 |
114 | #region Overrides of MappingRules
115 |
116 | protected override void Trace(Rule triggeredRule, string triggerProperty, ICdsTrade instance)
117 | {
118 | Console.WriteLine(triggeredRule);
119 | }
120 |
121 | #endregion
122 | }
123 | }
--------------------------------------------------------------------------------
/RulesEngine/Interceptors/DynamicWrapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Dynamic;
4 | using RulesEngine.RulesEngine;
5 | using RulesEngine.Tools;
6 |
7 | namespace RulesEngine.Interceptors
8 | {
9 | ///
10 | /// Wraps business object as a . It can be used tu intercept accessor calls on types that
11 | /// can not be wrapped in typed proxies (for example sealed classes)
12 | ///
13 | ///
14 | public sealed class DynamicWrapper : DynamicObject, INotifyPropertyChanged
15 | {
16 | ///
17 | /// The rules engine
18 | ///
19 | private readonly MappingRules _businessRules;
20 |
21 | private readonly object _root;
22 | private readonly object _wrappedObject;
23 |
24 |
25 | internal DynamicWrapper(object root, object obj, MappingRules businessRules)
26 | {
27 | _root = root;
28 | _wrappedObject = obj ?? throw new ArgumentNullException(nameof(obj));
29 | _businessRules = businessRules;
30 | }
31 |
32 |
33 | public DynamicWrapper(object root, MappingRules businessRules)
34 | {
35 |
36 | _wrappedObject = root ?? throw new ArgumentNullException(nameof(root));
37 | _root = root;
38 | _businessRules = businessRules;
39 | }
40 |
41 | #region Implementation of INotifyPropertyChanged
42 |
43 | public event PropertyChangedEventHandler PropertyChanged;
44 |
45 | #endregion
46 |
47 | private void OnPropertyChanged(string propertyName = null)
48 | {
49 | var handler = PropertyChanged;
50 | handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
51 | }
52 |
53 | #region Overrides of DynamicObject
54 |
55 | ///
56 | /// For the moment let the method calls pass through.
57 | ///
58 | ///
59 | ///
60 | ///
61 | ///
62 | public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
63 | {
64 | try
65 | {
66 | //call _wrappedObject object
67 | result = _wrappedObject.GetType()
68 | .GetMethod(binder.Name)
69 | ?.Invoke(_wrappedObject, args);
70 |
71 | return true;
72 | }
73 |
74 | catch
75 | {
76 | result = null;
77 | return false;
78 | }
79 | }
80 |
81 | ///
82 | /// Intercept calls to the property setters and divert them to the rules engine
83 | ///
84 | ///
85 | ///
86 | ///
87 | public override bool TrySetMember(SetMemberBinder binder, object value)
88 | {
89 | try
90 | {
91 | var modified = _businessRules.SetProperty(binder.Name, _root, _wrappedObject, value);
92 |
93 | foreach (var property in modified) OnPropertyChanged(property);
94 |
95 | return true;
96 | }
97 | catch
98 | {
99 | return false;
100 | }
101 | }
102 |
103 |
104 | private static bool IsComplexType(Type type)
105 | {
106 | if (type.IsValueType) return false;
107 |
108 | if (type == typeof(string)) return false;
109 |
110 | return type.IsClass;
111 | }
112 |
113 | ///
114 | /// Intercept calls to property getters. Let the invocation being done normally for now
115 | ///
116 | ///
117 | ///
118 | ///
119 | public override bool TryGetMember(GetMemberBinder binder, out object result)
120 | {
121 | try
122 | {
123 | var getter = CompiledAccessors.CompiledGetter(_wrappedObject.GetType(), binder.Name);
124 | var getterResult = getter(_wrappedObject);
125 |
126 | result = IsComplexType(getterResult.GetType())
127 | ? new DynamicWrapper(_root, getterResult, _businessRules)
128 | : getterResult;
129 |
130 | return true;
131 | }
132 | catch
133 | {
134 | result = null;
135 | return false;
136 | }
137 | }
138 |
139 | #endregion
140 | }
141 | }
--------------------------------------------------------------------------------
/RulesEngine/RulesEngine/Explain/LeafExpression.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Linq.Expressions;
4 | using System.Text;
5 |
6 | namespace RulesEngine.RulesEngine.Explain
7 | {
8 | ///
9 | /// The smallest expression we are able to interpret
10 | ///
11 |
12 | public sealed class LeafExpression : ExplainExpression
13 | {
14 | public string MemberName { get; set; }
15 |
16 | private HashSet