For a database design, all one needs is the left, operator, and right.
Something like this:
X > 10
A table like this:
RuleSetID PropertyName Operand TargetValue
1 Weight LessThan 200
Then one can use System.Linq.Expressions
namespace to dynamically create expressions for evaluation.
For more information on this .Net framework feature, please visit this link:
https://msdn.microsoft.com/en-us/library/system.linq.expressions(v=vs.110).aspx
All kinds of operators are supported, greater than, less than etc.
Now, some code to compile the expressions:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using RulesEngine.Model;
using System.Diagnostics;
namespace RulesEngine.Engine
{
public class ExpressionBuilder
{
public Func<T, bool> Compile<T>(Rule rule, Type type)
{
try
{
var param = Expression.Parameter(type);
Expression expression = BuildExpression<T>(rule, param);
return Expression.Lambda<Func<T, bool>>(expression, param).Compile();
}
catch (Exception exception)
{
//We would want to log this failure so the rule can be corrected.
Debug.WriteLine(exception.ToString());
//Return an expresssion that would always return false
Expression<Func<T, bool>> defaultLambda = x => false;
return defaultLambda.Compile();
}
}
private Expression BuildExpression<T>(Rule rule, ParameterExpression param)
{
var left = MemberExpression.Property(param, rule.PropertyName);
var propertyType = typeof(T).GetProperty(rule.PropertyName).PropertyType;
ExpressionType binaryExpressionType;
if (ExpressionType.TryParse(rule.Operand, out binaryExpressionType))
{
var right = Expression.Constant(Convert.ChangeType(rule.TargetValue, propertyType));
return Expression.MakeBinary(binaryExpressionType, left, right);
}
else
{
var methodName = propertyType.GetMethod(rule.Operand);
var parameterType = methodName.GetParameters()[0].ParameterType;
var right = Expression.Constant(Convert.ChangeType(rule.TargetValue, parameterType));
return Expression.Call(left, methodName, right);
}
}
}
}
A Rule would be a class to hold the definition from the database.
using System;
using System.Collections.Generic;
using System.Linq;
namespace RulesEngine.Model
{
public class Rule
{
public string PropertyName { get; private set; }
public string Operand { get; private set; }
public string TargetValue { get; private set; }
public Rule(string propertyName, string operand, string targetValue)
{
PropertyName = propertyName;
Operand = operand;
TargetValue = targetValue;
}
}
}
Now we create an object to hold the instance data. (Replace foo with your threshold instance data).
public class Foo
{
public string Name { get; private set; }
public int Weight { get; private set; }
public int Size { get; private set; }
public Foo(string name, int weight, int size)
{
Name = name;
Weight = weight;
Size = size;
}
}
And finally putting it all together...comparing instance data with the rule.
private static void RunExample()
{
var rule = new Rule("Weight","Equal", "100");
var foo = new Foo("FooName", 100, 200);
var expressionBuilder = new ExpressionBuilder();
Func<Foo, bool> fooRule = expressionBuilder.Compile<Foo>(rule, foo.GetType());
Console.WriteLine("Is weight equal to 100 for foo?" + fooRule(foo)); //true
}
Since it's generic you can use it as a mini rules engine that can take any instance data and compare it against a set of rules to return true/false.