Enhanced Dynamic Filtering

Remember my last post on dynamic filtering?
Well, this time I'm extending the code in order to allow two levels of querying:

Match type, represented by the following options:


public enum MatchType
{
	StartsWith = 0,

	Contains = 1
}

And word match:


public enum WordMatch
{
	AnyWord = 0,

	AllWords = 1,

	ExactPhrase = 2
}

You can combine the two levels in order to achieve the following combinations:

MatchType.StartsWith + WordMatch.AnyWord Matches any record that starts with any of the words specified
MatchType.StartsWith + WordMatch.AllWords Not available: does not make sense, throws an exception
MatchType.StartsWith + WordMatch.ExactPhrase Matches any record that starts with the exact specified phrase
MatchType.Contains + WordMatch.AnyWord Matches any record that contains any of the specified words
MatchType.Contains + WordMatch.AllWords Matches any record that contains all of the specified words
MatchType.Contains + WordMatch.ExactPhrase Matches any record that contains the exact specified phrase

Here is the code:


public static IList Search(this IQueryable query, Type entityType, String dataTextField, String phrase, MatchType matchType, WordMatch wordMatch, Int32 maxCount)
{
	String [] terms = phrase.Split(' ').Distinct().ToArray();
	StringBuilder result = new StringBuilder();
	PropertyInfo displayProperty = entityType.GetProperty(dataTextField);
	IList searchList = null;
	MethodInfo orderByMethod = typeof(Queryable).GetMethods(BindingFlags.Public | BindingFlags.Static).Where(m => m.Name == "OrderBy").ToArray() [ 0 ].MakeGenericMethod(entityType, displayProperty.PropertyType);
	MethodInfo takeMethod = typeof(Queryable).GetMethod("Take", BindingFlags.Public | BindingFlags.Static).MakeGenericMethod(entityType);
	MethodInfo whereMethod = typeof(Queryable).GetMethods(BindingFlags.Public | BindingFlags.Static).Where(m => m.Name == "Where").ToArray() [ 0 ].MakeGenericMethod(entityType);
	MethodInfo distinctMethod = typeof(Queryable).GetMethods(BindingFlags.Public | BindingFlags.Static).Where(m => m.Name == "Distinct" && m.GetParameters().Length == 1).Single().MakeGenericMethod(entityType);
	MethodInfo toListMethod = typeof(Enumerable).GetMethod("ToList", BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(entityType);

	MethodInfo matchMethod = typeof(String).GetMethod
	(
		(matchType == MatchType.StartsWith) ? "StartsWith" : "Contains",
		new Type [] { typeof(String) }
	);

	MemberExpression member = Expression.MakeMemberAccess
	(
		Expression.Parameter(entityType, "n"),
		displayProperty
	);

	MethodCallExpression call = null;
	LambdaExpression where = null;
	LambdaExpression orderBy = Expression.Lambda
	(
		member,
		member.Expression as ParameterExpression
	);

	switch (matchType)
	{
		case MatchType.StartsWith:
			switch (wordMatch)
			{
				case WordMatch.AnyWord:
					call = Expression.Call
					(
						member,
						matchMethod,
						Expression.Constant(terms [ 0 ])
					);

					where = Expression.Lambda
					(
						call,
						member.Expression as ParameterExpression
					);

					for (Int32 i = 1; i < terms.Length; ++i)
					{
						String term = terms [ i ];

						MethodCallExpression call2 = Expression.Call
						(
							member,
							matchMethod,
							Expression.Constant(term)
						);

						LambdaExpression where2 = Expression.Lambda
						(
							call2,
							member.Expression as ParameterExpression
						);

						var exp = Expression.Invoke(where2, where.Parameters.Cast<Expression>());

						where = Expression.Lambda
						(
							Expression.Or
							(
								where.Body,
								exp
							),
							where.Parameters.ToArray()
						);
					}
					break;

				case WordMatch.ExactPhrase:
					call = Expression.Call
					(
						member,
						matchMethod,
						Expression.Constant(phrase)
					);

					where = Expression.Lambda
					(
						call,
						member.Expression as ParameterExpression
					);
					break;

				case WordMatch.AllWords:
					throw (new Exception("The match type StartsWith is not supported with word match AllWords"));
			}

			break;

		case MatchType.Contains:
			switch (wordMatch)
			{
				case WordMatch.AnyWord:
					call = Expression.Call
					(
						member,
						matchMethod,
						Expression.Constant(terms [ 0 ])
					);

					where = Expression.Lambda
					(
						call,
						member.Expression as ParameterExpression
					);

					for (Int32 i = 1; i < terms.Length; ++i)
					{
						String term = terms [ i ];

						MethodCallExpression call2 = Expression.Call
						(
							member,
							matchMethod,
							Expression.Constant(term)
						);

						LambdaExpression where2 = Expression.Lambda
						(
							call2,
							member.Expression as ParameterExpression
						);

						var exp = Expression.Invoke(where2, where.Parameters.Cast<Expression>());

						where = Expression.Lambda
						(
							Expression.Or
							(
								where.Body,
								exp
							),
							where.Parameters.ToArray()
						);
					}
					break;

				case WordMatch.ExactPhrase:
					call = Expression.Call
					(
						member,
						matchMethod,
						Expression.Constant(phrase)
					);

					where = Expression.Lambda
					(
						call,
						member.Expression as ParameterExpression
					);
					break;

				case WordMatch.AllWords:
					call = Expression.Call
					(
						member,
						matchMethod,
						Expression.Constant(terms [ 0 ])
					);

					where = Expression.Lambda
					(
						call,
						member.Expression as ParameterExpression
					);

					for (Int32 i = 1; i < terms.Length; ++i)
					{
						String term = terms [ i ];

						MethodCallExpression call2 = Expression.Call
						(
							member,
							matchMethod,
							Expression.Constant(term)
						);

						LambdaExpression where2 = Expression.Lambda
						(
							call2,
							member.Expression as ParameterExpression
						);

						var exp = Expression.Invoke(where2, where.Parameters.Cast<Expression>());

						where = Expression.Lambda
						(
							Expression.AndAlso
							(
								where.Body,
								exp
							),
							where.Parameters.ToArray()
						);
					}
					break;
			}
			break;
	}

	query = orderByMethod.Invoke(null, new Object [] { query, orderBy }) as IQueryable;
	query = whereMethod.Invoke(null, new Object [] { query, where }) as IQueryable;

	if (maxCount != 0)
	{
		query = takeMethod.Invoke(null, new Object [] { query, maxCount }) as IQueryable;
	}

	searchList = toListMethod.Invoke(null, new Object [] { query }) as IList;

	return (searchList);
}

And this is how you'd use it:


IQueryable query = ctx.MyEntities;
IList list = Search(query, typeof(MyEntity), "Name", "Ricardo Peres", MatchType.Contains, WordMatch.ExactPhrase, 10 /*0 for all*/);

Bookmark and Share

                             

1 Comment

Comments have been disabled for this content.