views:

401

answers:

3

I have a search form that allows users to search on several different fields in several different ways. Here is an example of my code.

var claims = from c in db.Claims select c;

switch (ddlSearchField.Text)
{
    case "StartsWith":
        claims = claims.Where(c => c.companyFileID.StartsWith(txtSearchBox.Text));
        break;

    case "Equals":
        claims = claims.Where(c => c.companyFileID == txtSearchBox.Text);
        break;

    case "Contains":
        claims = claims.Where(c => c.companyFileID.Contains(txtSearchBox.Text));
        break;
}

I have about ten different fields that a user can search on so my outer switch statement is pretty large. There has to be a more elegant way to accomplish this.

A: 

Have you tried dynamic LINQ? It allows you to build string-based query statements similar to a SQL WHERE clause.

http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

pjabbott
A: 

You could make a dictionary of your possible predicates:

Dictionary<string, Func<string, Expression<Func<Claim, bool>>>> map = new Dictionary<string, Func<string, Expression<Func<Claim, bool>>>>() {
    { "StartsWith", t => c => c.companyFileID.StartsWith(t) },
    { "Equals",     t => c => c.companyFileID == t },
    { "Contains",   t => c => c.companyFileID.Contains(t) }
};

Which could be used like this:

var search = ddlSearchField.Text;
var text = txtSearchBox.Text;

var claims = from c in db.Claims select c;

Func<string, Expression<Func<Claim, bool>>> predicate = null;
if(dict.TryGetValue(search, out predicate))
    claims = claims.Where(predicate(text));
dahlbyk
+1  A: 

You could refactor some parts of the code by creating an extension method. Something like that :

static class QueryableExtensions
{
    private static MethodInfo StringContainsMethod;
    private static MethodInfo StringStartsWithMethod;

    static QueryableExtensions()
    {
        Type[] singleStringParam = new[] {typeof(string)};
        StringContainsMethod = typeof(string).GetMethod("Contains", singleStringParam);
        StringStartsWithMethod = typeof(string).GetMethod("StartsWith", singleStringParam);
    }

    public static IQueryable<T> AppendTextFilter<T>(this IQueryable<T> queryable, Expression<Func<T, string>> memberSelector, string condition, string value)
    {
        Expression expression = null;
        switch (condition)
        {
            case "StartsWith":
                expression = Expression.Call(
                                memberSelector.Body,
                                StringStartsWithMethod,
                                Expression.Constant(value));
                break;

            case "Equals":
                expression = Expression.Equal(
                                memberSelector.Body,
                                Expression.Constant(value));
                break;

            case "Contains":
                expression = Expression.Call(
                                memberSelector.Body,
                                StringContainsMethod,
                                Expression.Constant(value));
                break;

            default:
                throw new NotSupportedException(string.Format("'{0}' is not a supported condition", condition));
        }

        var lambda = Expression.Lambda<Func<T, bool>>(
                        expression,
                        memberSelector.Parameters);
        return queryable.Where(lambda);
    }
}

You could then use it like that :

var claims = db.Claims
             .AppendTextFilter(c => c.companyFileID, ddlSearchField.Text, txtSearchBox.Text)
             .AppendTextFilter(c => c.someOtherProperty, ddlOtherSearchField.Text, txtOtherSearchBox.Text)
             ...;
Thomas Levesque
Hi Thomas, your solution looks great and it is very close to what I am looking for. Is it safe to set the "claims" var to a gridview's datasource? I just tried that and received an error message.
tom d
Sorry, the error message was:Method 'System.Object DynamicInvoke(System.Object[])' has no supported translation to SQL.
tom d
OK, I see the problem... Linq to SQL can't convert a delegate call to a SQL expression, so it fails. I'll try to find a solution and update my answer
Thomas Levesque
Ok, thx for this.
tom d
OK, I fixed the code to use expressions rather than delegates. I only tested it with a list, but I think it should work fine with Linq to SQL. Please let me know !
Thomas Levesque
Very nice. If you want == instead of .Equals(), you can use Expression.Equal(memberSelector.Body, Expression.Constant(value)).
dahlbyk
@dahlbyk: I think you're right... I'm not sure the call to Equals will translate correctly to SQL. I'll update the code. Thanks !
Thomas Levesque
Thanks, I tried this out and it worked.
tom d