Last active
December 21, 2015 11:39
-
-
Save SoulFireMage/6300552 to your computer and use it in GitHub Desktop.
A generic constrained class that dynamically generates a rule engine based on reflection and specified properties-an expression tree is built for each property, compiled and added to a list of rules - this is generate but once for the lifetime of the instance. This is intended for use with a database migration testing engine.
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
Imports System.Reflection | |
Imports System.Linq.Expressions | |
''' <summary> | |
'''This generic class encapsulates the main algorithms for building a rule engine and a list of matched source/merge '''"rows"To prove whether a row in source and target are equivalent. | |
''' A property in the classes we may be using represent columns in a table or query. | |
'''Say we have an instance thus: | |
'''SourcePerson = New Person with {.Name = "ommitted db query", | |
''' .DOB =" ommitted db query"} | |
'''MergedPerson = New Person with {.Name = "ommitted db query", | |
''' .DOB =" ommitted db query"} | |
'''We add these instances to a List(of Person) so we get SourceList(of Person), MergeList(Of Person). | |
'''Then we would like to build some list where each item is a match pair of Persons. The engine below can be told to match '''on .Name and .DOB; it will generate the rules as needed and use them to decide if instances from each list are in fact, '''the same "fact". | |
''' | |
'''So we get a List(of MatchedPair(Of Person)) where MatchPair has members .SourcePerson and .MergePerson. In addition, '''using the IName, the MatchedPair instance will guarantee an Overrides ToString that returns a member from MergePerson (in '''this case). | |
''' | |
'''There is a weakness - it relies on the programmer to build adequate calling classes that use properties that adequately '''describe unique rows. Since some columns in a migration (unique ID's, some foreign keys) may be shifted, this is the next '''best thing. | |
''' | |
'''Using reflection we get the types and the propertylist passed by the calling class. | |
'''Expression trees are used to generate a lambda rule per property to test for equality. | |
'''The generic constraint provides the ToString member contract | |
'''The calling class can use the source, merge and Matched pair collections. | |
''' </summary> | |
'''<typeparam name="T"></typeparam> | |
'''<remarks></remarks> | |
Class BuildGenericMergeList(Of T As INamed) | |
Private Property _SourceList As New List(Of T) | |
Public ReadOnly Property SourceList As List(Of T) | |
Get | |
Return _SourceList | |
End Get | |
End Property | |
Private Property _MergeList As New List(Of T) | |
Public ReadOnly Property Mergelist As List(Of T) | |
Get | |
Return _MergeList | |
End Get | |
End Property | |
Private Property _unMatchedSource As New List(Of T) | |
Public ReadOnly Property unMatchedSource As List(Of T) | |
Get | |
Dim temp = mMatchedPairs.Select(Function(x) x.sourceItem).ToList | |
_unMatchedSource = temp.Except(_SourceList).ToList | |
Return _unMatchedSource | |
End Get | |
End Property | |
Private ReadOnly Property mDictBuild As Boolean | |
Get | |
Return _SourceList.Count = 0 And _MergeList.Count = 0 | |
End Get | |
End Property | |
Public Sub New(query As String, action As Action(Of String, dbConnect, List(Of T))) 'may need an overload for a dictionary as well. | |
_action = action | |
If query.Length > 0 Then BuildList(query) | |
End Sub | |
Private Sub BuildMatchingPairs(properties As List(Of PropertyInfo)) | |
For Each t In SourceList | |
Dim mergeItem As T = getMatchingMerge(t, properties) | |
If IsNothing(mergeItem) = False Then mMatchedPairs.Add(New MatchPair(Of T) With {.sourceItem = t, .mergeItem = mergeItem}) | |
Next | |
End Sub | |
Private Property mMatchedPairs As New List(Of MatchPair(Of T)) | |
Public ReadOnly Property MatchedPairs(properties As List(Of PropertyInfo)) As List(Of MatchPair(Of T)) | |
Get | |
If mMatchedPairs.Count = 0 Then BuildMatchingPairs(properties) | |
Return mMatchedPairs | |
End Get | |
End Property | |
Private Sub BuildList(query As String) | |
Dim dbset = New testingDBSet | |
If mDictBuild Then | |
'This implementation uses 3 databases. | |
For Each dbConnect In {dbset.getDbConnect(dbset.SourceDB), dbset.getDbConnect(dbset.TargetDB)} | |
BuildList(query, dbConnect, SourceList) | |
Next | |
BuildList(query, dbset.getDbConnect(dbset.MergedDB), Mergelist) | |
End If | |
End Sub | |
Private Property _action As Action(Of String, dbConnect, List(Of T)) | |
Private Sub BuildList(ByVal query As String, ByVal dbConnect As dbConnect, ByVal list As List(Of T)) | |
_action.Invoke(query, dbConnect, list) | |
End Sub | |
Private Property propDict As New Dictionary(Of PropertyInfo, PropertyInfo) | |
Private ReadOnly _rules As New List(Of Func(Of T, T, Boolean)) | |
Public Function getMatchingMerge(item As T, matchProperties As List(Of PropertyInfo)) As T | |
Dim getPropList = item.GetType().GetProperties.Where(Function(s) s.Equals(matchProperties.Where(Function(x) x.Name = s.Name).Select(Function(a) a).FirstOrDefault)).ToList | |
If propDict.Count < matchProperties.Count Then propDict = getPropList.ToDictionary(Function(p) matchProperties.Where(Function(s) s.Name = p.Name).First) | |
If _rules.Count <> matchProperties.Count Then | |
_rules.Clear() | |
For Each Lambda In From pair In propDict | |
Let leftProperty = pair.Key | |
Let rightProperty = pair.Value | |
Let leftParameter = Expression.Parameter(item.GetType, item.GetType.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(Of Func(Of T, T, Boolean))(DirectCast(leftEqualsRight, Expression), New ParameterExpression() {leftParameter, rightParameter}).Compile | |
_rules.Add(Lambda) | |
Next | |
End If | |
Dim _match = Mergelist.Where(Function(r) _rules.All(Function(x) x.Invoke(item, r) = True)).FirstOrDefault | |
Return _match | |
End Function | |
End Class | |
Public Class MatchPair(Of T As INamed) | |
Implements INamed | |
Property sourceItem As T | |
Property mergeItem As T | |
Overrides Function ToString() As String Implements INamed.Name | |
Return mergeItem.Name | |
End Function | |
End Class | |
Public Interface INamed | |
Function Name() As String | |
End Interface | |
'Used by calling classes to build a property list. | |
Module ReflectionUtility | |
Function GetMemberInfo(Of T)(expression As Expression(Of Func(Of T))) As MemberInfo | |
Dim member = TryCast(expression.Body, MemberExpression) | |
If member IsNot Nothing Then Return member.Member | |
Throw New ArgumentException("Expression is not a member", "expression") | |
End Function | |
End Module |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment