3

Is it possible to construct a valid Linq query which contains wildcard characters?

I've seen various answers to this question which suggest using:

.Where(entity => entity.Name.Contains("FooBar"))
.Where(entity => entity.Name.EndsWith("Bar")) 
.Where(entity => entity.Name.StartsWith("Foo"))

OR constructing RawSql:

var commandText =
    @"SELECT field
    FROM     table
    WHERE    field LIKE @search";

var query = new ObjectQuery<Profile>(commandText, context);
query.Parameters.Add(new ObjectParameter("search", wildcardSearch));

The first solution wouldn't work if the wildcard was not at the beginning or end of a string, for example, searchTerm = "Foo%Bar".

The second solution, using RawSql, doesn't sit right with me and feels like a cheap way out. But it does work.

The third option I have yet to try out is to create something which can parse the search term and construct a valid Linq query, which is something @Slauma had a go at in link 2 below. But this still wouldn't work if the wildcard was not at the beginning or end of the search term.

So the question: Is it possible to construct a valid Linq query which contains wildcard characters?

EDIT: Its worth mentioning that in this instance im using Oracle Data Access Components (ODAC/ODP), but I don't think it makes much difference in this case.

links:

1.“like” queries in Entity Framework

2.exact and wildcard searching conditional on search term

3.Using RawSql

Community
  • 1
  • 1
philreed
  • 2,497
  • 5
  • 26
  • 55
  • 1
    I know this is an old question, but you can use the idea from the answer here: http://stackoverflow.com/questions/1040380/wildcard-search-for-linq/42307642#42307642 There is not the possibility to add wildcards in the string, but you can construct the LIKE query you want. –  Feb 17 '17 at 22:25

3 Answers3

2

If you are using an EDMX file as the basis for your Entity Model then perhaps you could try creating a Conceptual Model Function that then performs a LIKE in SQL. I am not sure if this would work for Oracle. You should then be able to do something like the following:

.Where(entity => Like(entity.Name, "Foo%Bar"))
Adam Gritt
  • 2,654
  • 18
  • 20
  • I'm using Code First without an EDMX to an existing database but I'll look into it, thank you. – philreed Jul 18 '13 at 13:35
  • I'm struggling to find a way to do this without an EDMX, nice idea though. – philreed Jul 18 '13 at 15:40
  • @philreed I tried to find some information about doing similar with Code First but also came up empty. The one reference I found was from 2011 on a Microsoft Forum where it was a requested features but known to not be supported at that time. – Adam Gritt Jul 18 '13 at 16:23
1

use SqlFunctions.PatIndex, it would look like this:

.Where(entity => SqlFunctions.PatIndex("Foo%Bar", entity.Name) > 0)
BlackICE
  • 8,816
  • 3
  • 53
  • 91
  • sorry, just saw you're using Oracle, that may very well make a difference, not sure if patindex will work for that. – BlackICE Jul 18 '13 at 12:26
  • Yea I just updated the question to show I'm using Oracle, will be surprised if there isn't an equivalent. I'm looking into it now, thank you. – philreed Jul 18 '13 at 12:33
  • I cant find an Oracle version of PatIndex but it got me thinking about the potential of `string.IndexOfAny(searchTerm.Split('%'))`. Would need to amend slightly to make sure the split terms were in the correct sequence. – philreed Jul 18 '13 at 12:46
  • I highly doubt the EF provider is going to implement that. – BlackICE Jul 18 '13 at 13:02
0

I found a great solution for Oracle. This is part from another answer here, and part written by me.

    public static class LinqExtensions
{
    public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, String Name, String value)
    {
        Type model = typeof(T);
        ParameterExpression param = Expression.Parameter(typeof(T), "m");
        PropertyInfo key = model.GetProperty(Name);
        MemberExpression lhs = Expression.MakeMemberAccess(param, key);
        Expression<Func<T, String>> lambda = Expression.Lambda<Func<T, String>>(lhs, param);

        return source.Where(BuildLikeExpression(lambda, value));
    }
    public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, Expression<Func<T, String>> valueSelector, String value)
    {
        return source.Where(BuildLikeExpression(valueSelector, value));
    }
    public static Expression<Func<T, Boolean>> BuildLikeExpression<T>(Expression<Func<T, String>> valueSelector, String value)
    {
        if (valueSelector == null)
            throw new ArgumentNullException("valueSelector");
        value = value.Replace("*", "%");        // this allows us to use '%' or '*' for our wildcard
        if (value.Trim('%').Contains("%"))
        {
            Expression myBody = null;
            ParsedLike myParse = Parse(value);
            Type stringType = typeof(String);
            if(myParse.startwith!= null)
            {
                myBody = Expression.Call(valueSelector.Body, stringType.GetMethod("StartsWith", new Type[] { stringType }), Expression.Constant(myParse.startwith));
            }
            foreach (String contains in myParse.contains)
            {
                if (myBody == null)
                {
                    myBody = Expression.Call(valueSelector.Body, stringType.GetMethod("Contains", new Type[] { stringType }), Expression.Constant(contains));
                }
                else
                {
                    Expression myInner = Expression.Call(valueSelector.Body, stringType.GetMethod("Contains", new Type[] { stringType }), Expression.Constant(contains));
                    myBody = Expression.And(myBody, myInner);
                }
            }
            if (myParse.endwith != null)
            {
                if (myBody == null)
                {
                    myBody = Expression.Call(valueSelector.Body, stringType.GetMethod("EndsWith", new Type[] { stringType }), Expression.Constant(myParse.endwith));
                }
                else
                {
                    Expression myInner = Expression.Call(valueSelector.Body, stringType.GetMethod("EndsWith", new Type[] { stringType }), Expression.Constant(myParse.endwith));
                    myBody = Expression.And(myBody, myInner);
                }
            }
            return Expression.Lambda<Func<T, Boolean>>(myBody, valueSelector.Parameters.Single());
        }
        else
        {
            Expression myBody = Expression.Call(valueSelector.Body, GetLikeMethod(value), Expression.Constant(value.Trim('%')));
            return Expression.Lambda<Func<T, Boolean>>(myBody, valueSelector.Parameters.Single());
        }
    }
    private static MethodInfo GetLikeMethod(String value)
    {
        Type stringType = typeof(String);

        if (value.EndsWith("%") && value.StartsWith("%"))
        {
            return stringType.GetMethod("Contains", new Type[] { stringType });
        }
        else if (value.EndsWith("%"))
        {
            return stringType.GetMethod("StartsWith", new Type[] { stringType });
        }
        else
        {
            return stringType.GetMethod("EndsWith", new Type[] { stringType });
        }
    }
    private class ParsedLike
    {
        public String startwith { get; set; }
        public String endwith { get; set; }
        public String[] contains { get; set; }
    }
    private static ParsedLike Parse(String inValue)
    {
        ParsedLike myParse = new ParsedLike();
        String work = inValue;
        Int32 loc;
        if (!work.StartsWith("%"))
        {
            work = work.TrimStart('%');
            loc = work.IndexOf("%");
            myParse.startwith = work.Substring(0, loc);
            work = work.Substring(loc + 1);
        }
        if (!work.EndsWith("%"))
        {
            loc = work.LastIndexOf('%');
            myParse.endwith = work.Substring(loc + 1);
            if (loc == -1)
                work = String.Empty;
            else
                work = work.Substring(0, loc);
        }
        myParse.contains = work.Split(new[] { '%' }, StringSplitOptions.RemoveEmptyEntries);
        return myParse;
    }
}
Brian Kitt
  • 665
  • 1
  • 6
  • 20