Development With A Dot

Blog on development in general, and specifically on .NET

Sponsors

News

My Friends

My Links

Permanent Posts

Portuguese Communities

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

Comments

Robbye Rob said:

Just exactly what I was looking for.  Nice little article here.  I have put this to work in a GridView Filter Column.

# March 15, 2011 2:12 PM