doekman
doekman

Reputation: 19258

Is there a way to parameterize method in a linq query?

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

Answers (3)

Kris Vandermotten
Kris Vandermotten

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

Olivier
Olivier

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

Stefano Bafaro
Stefano Bafaro

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

Related Questions