Reputation: 19258
In my application with Linq to SQL, the user can search for text. An asterix (*) can be used at the beginning and/or end of the search expression. The code now is this:
var search = SearchTextBox.Text.Trim();
bool filterStartsWith = false, filterEndsWith = false;
if (!string.IsNullOrEmpty(search))
{
filterStartsWith = search.EndsWith("*");
filterEndsWith = search.StartsWith("*");
if (filterStartsWith) search = search.Substring(0, search.Length - 1);
if (filterEndsWith) search = search.Substring(1);
if (filterStartsWith)
{
if (filterEndsWith)
{
query = query.Where(item => item.Omschrijving.Contains(search));
}
else
{
query = query.Where(item => item.Omschrijving.StartsWith(search));
}
}
else
{
if (filterEndsWith)
{
query = query.Where(item => item.Omschrijving.EndsWith(search));
}
else
{
query = query.Where(item => item.Omschrijving == search);
}
}
}
However, I want to generalize this, because this kind of search happens on more places. Also, some tables, this should happen on more than one column. Any ideas?
I use Visual Studio 2010 with .NET Framework 4.0.
Upvotes: 2
Views: 4128
Reputation: 10201
You could try this:
static IQueryable<T> WhereColumnContains<T>(this IQueryable<T> source, Expression<Func<T, string>> selector, string search)
{
if (string.IsNullOrWhiteSpace(search))
{
return source;
}
Expression<Func<T, bool>> expression;
search = search.Trim();
var filterStartsWith = search.EndsWith("*");
var filterEndsWith = search.StartsWith("*");
if (filterEndsWith) search = search.Substring(1);
if (filterStartsWith)
{
search = search.Substring(0, search.Length - 1);
if (filterEndsWith)
{
var parameter = Expression.Parameter(typeof(T), "parameter");
expression = Expression.Lambda<Func<T, bool>>(
Expression.Call(Expression.Invoke(selector, parameter), typeof(string).GetMethod("Contains", new[] { typeof(string) }), Expression.Constant(search)),
parameter);
}
else
{
var parameter = Expression.Parameter(typeof(T), "parameter");
expression = Expression.Lambda<Func<T, bool>>(
Expression.Call(Expression.Invoke(selector, parameter), typeof(string).GetMethod("StartsWith", new[] { typeof(string) }), Expression.Constant(search)),
parameter);
}
}
else
{
if (filterEndsWith)
{
var parameter = Expression.Parameter(typeof(T), "parameter");
expression = Expression.Lambda<Func<T, bool>>(
Expression.Call(Expression.Invoke(selector, parameter), typeof(string).GetMethod("EndsWith", new[] { typeof(string) }), Expression.Constant(search)),
parameter);
}
else
{
var parameter = Expression.Parameter(typeof(T), "parameter");
expression = Expression.Lambda<Func<T, bool>>(
Expression.Equal(Expression.Invoke(selector, parameter), Expression.Constant(search)),
parameter);
}
}
return source.Where(expression);
}
Call it as follows:
query = query.WhereColumnContains(item => item.Omschrijving, search);
Upvotes: 3
Reputation: 5688
You could use dynamically built expressions.
Here is a sample code to implement StartWith on multiple columns (may not compile, i'm typing it directly in stackoverflow)- just add support to other methods...
This code works on all linq-to-sql queries, assuming "columnsToSearch" is always refering to string properties.
IQueryable myQuery = ... your query you want to filter ...;
string searchWhat = "text to search";
bool startsWith;
Expression condition = null;
var p = Expression.Parameter(myQuery.ElementType,"x");
foreach (string column in columnsToSearch)
{
if (startsWith)
{
var myCondition = Expression.Call(Expression.PropertyOrField(p, column),
typeof (string).GetMethod("StartsWith"),
Expression.Constant(searchWhat));
if (condition == null)
condition = myCondition;
else
condition = Expression.OrElse(condition, myCondition);
}
}
var newQuery = Expression.Call(typeof (Queryable).GetMethod("Where"), myQuery.Expression, Expression.Lambda(condition, p));
var myQueryFiltered = myQuery.Provider.CreateQuery(newQuery);
In a nutshell, if you had a query
myRepository.Where(t=>t.Mycondition)
this code will generate a new query like myRepository.Where(t=>t.Mycondition).Where(t=>t.Field1.StartsWith("a")||t.Field2.StartWith("a"))
(depending on which columns you gave to it of course)
Then, all you have to do is to cast back "myQueryFiltered" to IEnumerable or IQueryable in order to execute it an get filtered results :)
Upvotes: 0
Reputation: 913
You can use a Strategy Pattern. You will have 4 strategies based on the "search" value and with a "bool CanHandle(search)" method, a factory the create a List of strategies, and your program will call a client that simply create a new factory, call a method "BuildQuery(search)" that execute the right strategy found with the CanHandle method, and return a query value.
http://en.wikipedia.org/wiki/Strategy_pattern
Upvotes: 0