Your PC can't even




C# - SearchInModel<T>

This post documents how to create a generic method capable for searching for a fragment of text within a collection of objects.

Why? How is this useful?

  • Having a search algorithim capable of digging into a desired objects for a fragment of text is quite useful. Making it generic so that it is reusable is a nice bonus! I often use this when implementing an admin interface for users such that they can search across a data-set easier.

  • Here is an example implementing this in MVC.

Implementing the methods

Find Key Member
		
/// Given an object, use reflection to find a property passed in.
protected Tuple<PropertyInfo, object> FindKeyMember(object inObj, PropertyInfo ToFind) {
    Tuple<PropertyInfo, object> PropAndParent = null;
            try {
            var ms = inObj.GetType().GetRuntimeProperties().ToList();
            foreach (var prop in ms) {
            var value = prop.GetValue(inObj);
            if (value != null) {
            if (value.GetType().IsValueType || value.GetType().IsPrimitive || value.GetType() == typeof(string)) {
            if (prop.MemberType == ToFind.MemberType && prop.Name == ToFind.Name && prop.ReflectedType == ToFind.ReflectedType) {
                        PropAndParent = new Tuple<PropertyInfo, object>(prop, inObj);

            break;
                    }
                }
                
            else if (value is IList) {
                    Type itemType = value.GetType().GetInterfaces().SelectMany(x => x.GetGenericArguments().Select(z => z)).First();
            var SubProperties = itemType.GetRuntimeProperties().ToList();
            foreach (var item in (IList)value) {
            var Member = FindKeyMember(item, ToFind);
            if (Member != null) {
                            PropAndParent = Member;
            break;
                        }
                    }
                }
            else {
            //Not a value recurse
            var Member = FindKeyMember(value, ToFind);
            if (Member != null) {
                        PropAndParent = Member;
            break;
                    }
                }
            }
        }
    }
            catch (Exception e) {
    }
            return PropAndParent;
}
		
		
Recurse Reflect Compare
		
/// Overload for when the SelectedChunk are of the same type as the starting object
protected void RecurseReflectCompare<T>(PropertyInfo y, string Fragment, T start, object parent, Expression<Func<T, object>> outExpr, ref HashSet<Type> _foundIn, ref List<T> _selectedChunks, ref HashSet<Guid> _consumed, HashSet<Type> NodeChain = null) {
            //Evaluate the expression this should give us a primary key to match on

    MemberExpression expr = null;
            if (outExpr.Body is MemberExpression) {
        expr = ((MemberExpression)outExpr.Body);
    }
            else {
            var op = ((UnaryExpression)outExpr.Body).Operand;
        expr = ((MemberExpression)op);
    }
            var prop = (PropertyInfo)expr.Member;
            try {
            var Value = y.GetValue(parent);
            if (Value != null && (NodeChain == null || !NodeChain.Contains(Value.GetType()))) {
            List<PropertyInfo> SubProperties = new List<PropertyInfo>();
            //This is a list, recurse all the props to ensure we handle the lists
            if (Value is IList) { //&& Value.GetType().IsGenericType && Value.GetType().GetGenericTypeDefinition().IsAssignableFrom(typeof(List<>))) {
                Type itemType = Value.GetType().GetInterfaces().SelectMany(x => x.GetGenericArguments().Select(z => z)).First();
            if ((NodeChain == null || !NodeChain.Contains(itemType))) {
                    SubProperties = itemType.GetRuntimeProperties().ToList();
            foreach (var item in (IList)Value) {
            foreach (var _subProp in SubProperties) {
            if (NodeChain == null) {
                                NodeChain = new HashSet<Type>();
                            }
                            NodeChain.Add(parent.GetType());
                            RecurseReflectCompare<T>(_subProp, Fragment, start, item, outExpr, ref _foundIn, ref _selectedChunks, ref _consumed, NodeChain);
                        }
                    }
                }
            }
            else if (Value.GetType().IsValueType || Value.GetType().IsPrimitive || Value.GetType() == typeof(string)) {
            //Check the value first, if this condition is met that means we are recursing from an IList call. 
            //Means an actual value was assigned in the first line of this method, so we have a primitive type
            if (Value.ToString().ToLower().Contains(Fragment.ToLower())) {
            try {
            //We need to match the target prop field to the one we found
            //Iterate from the root to find the key, we do this so we can add it to a consumed list
                        Tuple<PropertyInfo, object> Key = FindKeyMember(start, prop);
            var KeyValue = Key.Item1.GetValue(Key.Item2);
            if (!_consumed.Contains(new Guid(KeyValue.ToString()))) {
                            _foundIn.Add(parent.GetType());
                            _selectedChunks.Add((T)Convert.ChangeType(start, typeof(T)));
                            _consumed.Add(new Guid(KeyValue.ToString()));
                        }
                    }
            catch (Exception e) {
                    }
                }
            }
            //Ignore entityset its huge
            else if (Value.GetType().IsClass && Value.GetType().IsPublic) {//&& !Value.GetType().Name.ToLower().Contains("entityset")) {
                SubProperties = Value.GetType().GetRuntimeProperties().ToList();
            foreach (var z in SubProperties) {
            try {
            //Some Kind of List , class struct etc; and nodes in our DFS we have encountered in the tree
            if (NodeChain == null) {
                            NodeChain = new HashSet<Type>();
                        }
                        NodeChain.Add(parent.GetType());
                        RecurseReflectCompare<T>(z, Fragment, start, Value, outExpr, ref _foundIn, ref _selectedChunks, ref _consumed, NodeChain);
                    }
            catch {
            //Generally This error will be DataContext accessed after dispose, supress and continue.

                    }
                }
            }
            else { }
        }
    }
            catch (Exception e) {
    }
}
		
		
Search In Model
		
protected void SearchInModel<T>(string Fragment, List<T> Model, Expression<Func<T, object>> outExpr, out HashSet<Type> FoundIn, out List<T> SelectedChunks) {
    HashSet<Type> _FoundIn = new HashSet<Type>();
    HashSet<Guid> Consumed = new HashSet<Guid>();
    List<T> _SelectedChunks = new List<T>();
    List<PropertyInfo> DestinationProperties = typeof(T).GetRuntimeProperties().ToList();
            foreach (var root in Model) {
            foreach (var currentProperty in DestinationProperties) {
            RecurseReflectCompare<T>(currentProperty, Fragment, root, root, outExpr, ref _FoundIn, ref _SelectedChunks, ref Consumed);
        }
    }
    FoundIn = _FoundIn;
    SelectedChunks = _SelectedChunks;
}
		
		
Usage.
Searching on a collection provided by DBML.
			
string Fragment = "Find me!";
HashSet<Type>FoundIn = new HashSet<Type>();
List<YourTable> SelectedChunks = new List<YourTable>();
using (WebDbDataContext context = new WebDbDataContext(Constants.WebConnectionString)) {
            base.SearchInModel(Fragment, context.YourTable.ToList(), d => d.Id, out FoundIn, out SelectedChunks);
}