43

I would like to know if it is possible to do a wildcard search using LINQ.

I see LINQ has Contains, StartsWith, EndsWith, etc.

What if I want something like %Test if%it work%, how do I do it?

Regards

PlayKid
  • 1,345
  • 5
  • 20
  • 33

14 Answers14

85

You can use SqlMethods.Like().

An example of the usage:

var results =
        from u in users
        where SqlMethods.Like(u.FirstName, "%John%")
        select u;
Ryan Versaw
  • 6,417
  • 3
  • 30
  • 31
41

I would use Regular Expressions, since you might not always be using Linq to SQL.

Like this example of Linq to Objects

List<string> list = new List<string>();
list.Add("This is a sentence.");
list.Add("This is another one.");
list.Add("C# is fun.");
list.Add("Linq is also fun.");

System.Text.RegularExpressions.Regex regEx = new System.Text.RegularExpressions.Regex("This");

var qry = list
    .Where<string>(item => regEx.IsMatch(item))
    .ToList<string>();

// Print results
foreach (var item in qry)
{
    Console.WriteLine(item);
}
David Basarab
  • 72,212
  • 42
  • 129
  • 156
14

add System.Data.Linq.SqlClient to your using or imports list then try:

var results= from x in data
             where SqlMethods.Like(x.SearchField, “%something%like%this%”)
             select x;
Joe Davis
  • 1,019
  • 8
  • 14
14

For Entity Framework Core 2.0 there is LIKE operator (announced in August 2017):

var query = from e in _context.Employees
                    where EF.Functions.Like(e.Title, "%developer%")
                    select e;
Dmitry Pavlov
  • 30,789
  • 8
  • 97
  • 121
6

Looking at the question

What if I want something like %Test if%it work%, how do I do it?

then I am expecting something of

LIKE '%Test if%it work%'

meaning that the string must contain 'Test if' and 'it work', in that order.

This will not work:

context.SomeTable.Where(s => s.Name.Contains("Test if%it work")).ToList();

And if I use:

context.SomeTable.Where(s => s.Name.Contains("Test if") && s.Name.Contains("it work")).ToList();

then I will find all records that contain both "Test if" and "it work", but not specifically in that order.

So with Contains this is not possible. But with IndexOf it is.

IndexOf will locate the searchstring AND return the position of it in the string. Making it possible to find the words in the correct order.

-- Update --

With my original answer it was not my goal to provide a generic solution, but rather an example of another approach that is not sql dependend. So it is correct that the original example only answers the literal question. But since the answer may be more useful if it is generic, I've written an IQuerable extension that allows to add a like statement to the query as easy as a where statement. The extension works for both Linq as Linq-Sql.

This will find all records with both "Test if" and "it work", in that order.

context.SomeTable.Like("test if%it work", "Name").ToList();

listOfString.Like("test if%it work").ToList();

Extension, allows any number of wildcards:

/// <summary>
/// Allow to search the string with wildcards.
/// </summary>
/// <typeparam name="T">String or an object with a string member.</typeparam>
/// <param name="q">Original query</param>
/// <param name="searchstring">The searchstring</param>
/// <param name="memberName">The name of the field or null if not a field.</param>
/// <returns>Query filtered by 'LIKE'.</returns>
public static IQueryable<T> Like<T>(this IQueryable<T> q, string searchstring, string memberName = null)
{
    // %a%b%c% --> IndexOf(a) > -1 && IndexOf(b) > IndexOf(a) && IndexOf(c) > IndexOf(b)

    var eParam = Expression.Parameter(typeof(T), "e");

    MethodInfo methodInfo;

    // Linq (C#) is case sensitive, but sql isn't. Use StringComparison ignorecase for Linq.
    // Sql however doesn't know StringComparison, so try to determine the provider.
    var isLinq = (q.Provider.GetType().IsGenericType && q.Provider.GetType().GetGenericTypeDefinition() == typeof(EnumerableQuery<>));
    if (isLinq)
        methodInfo = typeof(string).GetMethod("IndexOf", new[] { typeof(string), typeof(StringComparison) });
    else
        methodInfo = typeof(string).GetMethod("IndexOf", new[] { typeof(string) });

    Expression expr;
    if (string.IsNullOrEmpty(memberName))
        expr = eParam;
    else
        expr = Expression.Property(eParam, memberName);

    // Split the searchstring by the wildcard symbol:
    var likeParts = searchstring.Split(new char[] { '%' }, StringSplitOptions.RemoveEmptyEntries);

    for (int i = 0; i < likeParts.Length; i++)
    {
        MethodCallExpression e;
        if (isLinq)
            e = Expression.Call(expr, methodInfo, new Expression[] { Expression.Constant(likeParts[i], typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase) });
        else
            e = Expression.Call(expr, methodInfo, Expression.Constant(likeParts[i], typeof(string)));

        if (i == 0)
        {
            // e.IndexOf("likePart") > -1
            q = q.Where(Expression.Lambda<Func<T, bool>>(Expression.GreaterThan(e, Expression.Constant(-1, typeof(int))), eParam));
        }
        else
        {
            // e.IndexOf("likePart_previous")
            MethodCallExpression ePrevious;
            if (isLinq)
                ePrevious = Expression.Call(expr, methodInfo, new Expression[] { Expression.Constant(likeParts[i - 1], typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase) });
            else
                ePrevious = Expression.Call(expr, methodInfo, Expression.Constant(likeParts[i - 1], typeof(string)));

            // e.IndexOf("likePart_previous") < e.IndexOf("likePart")
            q = q.Where(Expression.Lambda<Func<T, bool>>(Expression.LessThan(ePrevious, e), eParam));
        }
    }
    return q;
}

Since it doesn't need SqlMethods I assume you can use this for any database, like MySql or Postgresql. But I do not know for sure. I did test this with Sql Server using Entity Framework 6. The above statement generates the following code in Sql Server.

SELECT [Extent1].* FROM SomeTable AS [Extent1]
WHERE ((( CAST(CHARINDEX(N'test if', [Extent1].[Name]) AS int)) - 1) > -1)
AND ((( CAST(CHARINDEX(N'test if', [Extent1].[Name]) AS int)) - 1) < 
     (( CAST(CHARINDEX(N'it work', [Extent1].[Name]) AS int)) - 1))

About performance, there seems to be some discussion about what is 'better': LIKE or CHARINDEX. And from what I've read CHARINDEX seems to be favorite.

  • Your answer doesn't contain an answer to the general question of how to implement wildcard search in LINQ, it just answers the specific example, and will not work for any other circumstance. As such, it doesn't provide a useful answer to the question. – Frosty840 Mar 01 '17 at 14:44
  • Actually I was looking for a SQL specific answer, as it annoys me to have to write extra generic code that I will never need since we will always be using SQL Server. Unfortunately while you showed why contains will not work you did not show anything as simple that would. The comparison I am trying to make is one of a string of contains. So I was hoping for something simple to add to that string. It looks like SQLMethods.Like will have to be used. – user1161391 Dec 20 '17 at 17:22
4

I know this is and old topic, but here is my very simple solution:

string s=Regex.Escape("pattern - escaped for sanity").Replace("%", ".*").Replace("_", ".?");
user => Regex.IsMatch(user.FullName, s, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);

In this code, I am using common escape characters for the SQL language. If you want to use say * and ?, escaped string will contain \* and \? correspondingly, make sure to include the backslash character in the .Replace(...) statement(s). Of course, if you want to give your user the ability to RexEx search, just don't escape the pattern string.

Search Regex tutorial for other options.

I believe normally % will match at least one character, while the RegEx .* will match zero or more characters. So in reality, the % wildcard is more like .+ (greedy) rather than .* (lazy).

Hope this helps.

nurchi
  • 770
  • 11
  • 24
3
.Where( column LIKE "Pattern")
Rony
  • 9,331
  • 2
  • 22
  • 22
2
var result = (from x in db.Members
              where x.IDNumber.Contains(idnumber)
              && x.InstitutionIdentifier == institution.Identifier
              select x).ToList();
return result;

Will work for both Linq to SQL and Linq in memory.

Gerhard
  • 29
  • 1
1

not sure if you talk LinqToSql or just linq... but you could regular expressions like this:

.Where(dto => System.Text.RegularExpressions.Regex.IsMatch(dto.CustomerName, @"Ad"));
bytebender
  • 7,371
  • 2
  • 31
  • 54
1

In .Net code including LINQ to Objects, I am using implementation of IsSqlLikeMatch function from thread Using Regex to create a SQL's "like" like function..

Example of use

bool ret = message.IsSqlLikeMatch(pattern);

More details in my post SQL's "like" patterns to compare in .Net

Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
1

You can also use "contains"

var myresult = db.MyItems.Where(x=>x.MyField.Contains(mysearchstring));
spadelives
  • 1,588
  • 13
  • 23
0

Are you talking LINQ to objects or LINQ to SQL?

For LINQ to objects you'll have to resort to regular expressions me thinks.

fretje
  • 8,322
  • 2
  • 49
  • 61
0

I use this for supporting a wildcard filter of "*" in a user's search. (order does not matter):

 if (!string.IsNullOrEmpty(SearchString))
    {
     List<String> containValues = new List<String>();
     if (SearchString.Contains("*"))
        {

        String[] pieces = SearchString.Split("*");

        foreach (String piece in pieces)
                {
                if (piece != "")
                   {
                   containValues.Add(piece);
                   }
                 }
           }

       if (containValues.Count > 0)
          {
          foreach(String thisValue in containValues)
             {
             Items = Items.Where(s => s.Description.Contains(thisValue));
             }
           }
           else
           {
           Items = Items.Where(s => s.Description.Contains(SearchString));
           }
       }
pcalkins
  • 1,188
  • 13
  • 20
0

I have extended Ruard van Elburg's example to support my needs, and thought I would share. It handles wildcards such as "a%" (startswith(a)), "%b" (endswith(b)), "a%b" (startswith(a) && endswith(b)), and "a%b%c" (startwith(a), indexof(a) < indexof(b), & endswith(c) ).

    public static class LinqLikeExtension
{
    /// <summary> Permits searching a string value with any number of wildcards. This was written 
    /// to handle a variety of EF wildcard queries not supported because the current version is 
    /// less tan EFv6.2, which has a .Like() method.
    /// like in EFv6.</summary>
    /// <typeparam name="T">String or an object with a string member.</typeparam>
    /// <param name="query">Original query</param>
    /// <param name="searchstring">The searchstring</param>
    /// <param name="columnName">The name of the db column, or null if not a column.</param>
    /// <returns>Query filtered by 'LIKE'.</returns>
    /// <example>return iQueryableRows.Like("a", "ReferenceNumber");</example>
    /// <example>return iQueryableRows.Like("a%", "ReferenceNumber");</example>
    /// <example>return iQueryableRows.Like("%b", "ReferenceNumber");</example>
    /// <example>return iQueryableRows.Like("a%b", "ReferenceNumber");</example>
    /// <example>return iQueryableRows.Like("a%b%c", "ReferenceNumber");</example>
    /// <remarks>Linq (C#) is case sensitive, but sql isn't. Use StringComparison ignorecase for Linq.
    /// Keep in mind that Sql however doesn't know StringComparison, so try to determine the provider.</remarks>
    /// <remarks>base author -- Ruard van Elburg from StackOverflow, modifications by dvn</remarks>
    /// <seealso cref="https://stackoverflow.com/questions/1040380/wildcard-search-for-linq"/>
    public static IQueryable<T> Like<T>(this IQueryable<T> query, string searchstring, string columnName = null)
    {
        var eParam = Expression.Parameter(typeof(T), "e");
        var isLinq = (query.Provider.GetType().IsGenericType && query.Provider.GetType().GetGenericTypeDefinition() == typeof(EnumerableQuery<>));

        MethodInfo IndexOf, StartsWith, EndsWith, Equals;
        MethodCallExpression mceCurrent, mcePrevious;

        Expression method = string.IsNullOrEmpty(columnName) ? eParam : (Expression)Expression.Property(eParam, columnName);

        var likeParts = searchstring.Split(new char[] { '%' });

        for (int i = 0; i < likeParts.Length; i++)
        {
            if (likeParts[i] == string.Empty) continue; // "%a"

            if (i == 0)
            {
                if (likeParts.Length == 1) // "a"
                {
                    Equals = isLinq
                        ? Equals = typeof(string).GetMethod("Equals", new[] { typeof(string), typeof(StringComparison) })
                        : Equals = typeof(string).GetMethod("Equals", new[] { typeof(string) });
                    mceCurrent = isLinq
                        ? Expression.Call(method, Equals, new Expression[] { Expression.Constant(likeParts[i], typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase) })
                        : Expression.Call(method, Equals, Expression.Constant(likeParts[i], typeof(string)));
                }
                else // "a%" or "a%b"
                {
                    StartsWith = isLinq
                        ? StartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string), typeof(StringComparison) })
                        : StartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
                    mceCurrent = isLinq
                        ? Expression.Call(method, StartsWith, new Expression[] { Expression.Constant(likeParts[i], typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase) })
                        : Expression.Call(method, StartsWith, Expression.Constant(likeParts[i], typeof(string)));
                }
                query = query.Where(Expression.Lambda<Func<T, bool>>(mceCurrent, eParam));
            }
            else if (i == likeParts.Length - 1)  // "a%b" or "%b"
            {
                EndsWith = isLinq
                    ? EndsWith = typeof(string).GetMethod("EndsWith", new[] { typeof(string), typeof(StringComparison) })
                    : EndsWith = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
                mceCurrent = isLinq
                    ? Expression.Call(method, EndsWith, new Expression[] { Expression.Constant(likeParts[i], typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase) })
                    : Expression.Call(method, EndsWith, Expression.Constant(likeParts[i], typeof(string)));
                query = query.Where(Expression.Lambda<Func<T, bool>>(mceCurrent, eParam));
            }
            else // "a%b%c"
            {
                IndexOf = isLinq
                    ? IndexOf = typeof(string).GetMethod("IndexOf", new[] { typeof(string), typeof(StringComparison) })
                    : IndexOf = typeof(string).GetMethod("IndexOf", new[] { typeof(string) });
                mceCurrent = isLinq
                    ? Expression.Call(method, IndexOf, new Expression[] { Expression.Constant(likeParts[i], typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase) })
                    : Expression.Call(method, IndexOf, Expression.Constant(likeParts[i], typeof(string)));
                mcePrevious = isLinq
                    ? Expression.Call(method, IndexOf, new Expression[] { Expression.Constant(likeParts[i - 1], typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase) })
                    : Expression.Call(method, IndexOf, Expression.Constant(likeParts[i - 1], typeof(string)));
                query = query.Where(Expression.Lambda<Func<T, bool>>(Expression.LessThan(mcePrevious, mceCurrent), eParam));
            }
        }

        return query;
    }
}

I understand this is really late, and I understand EFv6.2+ supports a Like() method. But maybe you are like me, in a small shop with large legacy applications that make it difficult to simply upgrade .Net and EF versions.

donvnielsen
  • 195
  • 1
  • 9