Last active
November 16, 2015 07:28
-
-
Save SoulFireMage/08ecabf0300420ad1dd7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using System.Reflection; | |
/// <summary> | |
/// Dynamic rules engine, translated from visual basic | |
/// Demonstration of demonstrating how to build a generic dynamic rules engine. | |
/// Original purpose: arbitrary classes are generated to reflect types from tables in a database | |
/// Two databases were being merged and a method of checking arbitrary rows in any table was required | |
/// The datalayer translated 1 table = 1 class and primary keys were autonumber and could not be relied upon | |
/// to guarantee that a fact (table row as an instance of it's class) was identical from the original databases | |
/// and the new merged database. This was a legacy schema. | |
/// | |
/// The rules engine below would allow for a list of any class type to be used, you had to | |
/// define what columns must match in order for the row from table in db1 to be equal to the row in the othogonal table | |
/// in DB2. | |
/// | |
/// It relies on a manual definition of what the correct columns are and simply helped cut the code to be written down. | |
/// | |
/// At the time of writing, the legacy codebase did not use Entity Framework or any other ORM. | |
/// | |
/// Key points of the exercise: | |
/// Demonstrate understand of generics and the idea of a template pattern. | |
/// Demonstrate use of interfaces as a means of providing the type/member contracts needed. | |
/// Use of datastructures, code as data and linq | |
/// Use of reflection and expression trees. | |
/// | |
/// Some methods are skipped in translation as they don't add clarity to this code (database access etc). | |
/// | |
/// Important Note: | |
/// I would use T4 or other code gen tools plus a config file (Jason property list) for this job now and | |
/// over head of actually implementing this approach! | |
/// </summary> | |
class RulesEngine<T> where T : INamed | |
{ | |
private List<T> _SourceList = new List<T>(); | |
public List<T> SourceList | |
{ | |
get { return _SourceList; } | |
} | |
private List<T> _mergeList = new List<T>(); | |
public List<T> MergeList | |
{ | |
get{ return _mergeList;} | |
} | |
private void BuildMatchingPairs(List<PropertyInfo> properties) | |
{ | |
foreach (T t in SourceList) | |
{ | |
T mergeItem = getMatchingMerge(t, properties); | |
if (mergeItem == null) | |
{ | |
mMatchedPairs.Add(new MatchPair<T> { sourceItem = t, mergeItem = mergeItem }); | |
} | |
} | |
} | |
private List<MatchPair<T>> mMatchedPairs = new List<MatchPair<T>>(); | |
/// <summary> | |
/// These are the matched pairs from the left table and right table class instances. | |
/// We use getmatchingMerge to find our ideal partner. | |
/// </summary> | |
/// <param name="properties"></param> | |
/// <returns></returns> | |
public List<MatchPair<T>> MatchedPairs(List<PropertyInfo> properties) | |
{ | |
if (mMatchedPairs.Count == 0) | |
{ | |
BuildMatchingPairs(properties); | |
} | |
return mMatchedPairs; | |
} | |
private Action<string, dbConnect, List<T>> _action; | |
private void BuildList(string query, dbConnect dbConnect, List<T> list) | |
{ | |
_action.Invoke(query, dbConnect, list); | |
} | |
private Dictionary<PropertyInfo, PropertyInfo> propDict = new Dictionary<PropertyInfo, PropertyInfo>(); | |
private readonly List<Func<T, T, bool>> _rules; | |
/// <summary> | |
/// The Dynamic Rules Engine itself | |
/// Here we have to pass in a class instance and the properties to be matched. | |
/// using reflection to get the members, put them in a list and generate a list of lambda predicates expressions | |
/// We compile and run these - they all have to be true to get a match. We return a match item, if one exists. | |
/// </summary> | |
private T getMatchingMerge(T item, List<PropertyInfo> matchProperties) | |
{ | |
List<PropertyInfo> getPropList = item.GetType().GetProperties().Where(s => | |
s.Equals(matchProperties.Where(x => x.Name == s.Name).FirstOrDefault())).ToList(); | |
if (propDict.Count < matchProperties.Count) | |
{ | |
propDict = getPropList.ToDictionary(p => matchProperties.Where(s => s.Name == p.Name).First()); | |
} | |
if (_rules.Count != matchProperties.Count) { | |
_rules.Clear(); | |
foreach (var lambda in from Pair in propDict | |
let leftProperty = Pair.Key | |
let rightProperty = Pair.Value | |
let leftParameter = Expression.Parameter(rightProperty.DeclaringType, rightProperty.DeclaringType.Name) | |
let rightParameter = Expression.Parameter(rightProperty.DeclaringType, rightProperty.DeclaringType.Name) | |
let left = Expression.Property(leftParameter, leftProperty) | |
let right = Expression.Property(rightParameter, rightProperty) | |
let leftEqualsRight = Expression.Equal(left, right) | |
// select Expression.Lambda<Func<T, T, bool>>(leftEqualsRight, Expression), new Parameter.Expression() { leftParameter, rightParameter }).Compile() | |
select Expression.Lambda<Func<T, T, bool>>((Expression)leftEqualsRight, new ParameterExpression[] { leftParameter, rightParameter }).Compile()) | |
_rules.Add(lambda); | |
} | |
T _match = MergeList.Where(r => _rules.All(x => x.Invoke(item, r) == true)).FirstOrDefault(); | |
return _match; //I've not covered the path where there is no match! | |
} | |
private List<T> _unMatchedSource = new List<T>(); | |
public List<T> UnmatchedSource | |
{ | |
get { | |
return _unMatchedSource; } | |
} | |
} | |
public class MatchPair<T> : INamed where T : INamed | |
{ | |
public T sourceItem { get; set; } | |
public T mergeItem { get; set; } | |
public override string ToString() | |
{ | |
return mergeItem.Name(); | |
} | |
string INamed.Name() | |
{ | |
return ToString(); | |
} | |
} | |
public interface INamed | |
{ | |
string Name(); | |
} | |
//Dummy class | |
public class dbConnect | |
{ | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment