8

I'm using LINQ 2 Entities. Following is the problem:

string str = '%test%.doc%' 
.Contains(str) // converts this into LIKE '%~%test~%.doc~%%'

Expected Conversion: LIKE '%test%.doc%'

If it was LINQ 2 SQL, I could have used SqlMethods.Like as somebody answered it in my previous question. But now as I'm using L2E not L2S, I need other solution.

frast
  • 2,700
  • 1
  • 25
  • 34
ns12345
  • 3,079
  • 5
  • 41
  • 62
  • For Now I created a sql string dynamically and execute it through ExecuteStoreQuery and solves my problem. – ns12345 Feb 03 '11 at 23:08

6 Answers6

17

The SQL method PATINDEX provides the same functionality as LIKE. Therefore, you can use the SqlFunctions.PatIndex method:

.Where(x => SqlFunctions.PatIndex("%test%.doc%", x.MySearchField) > 0)
BG100
  • 4,481
  • 2
  • 37
  • 64
  • Worked like a charm. Thanks BG! – Mark Rucker Apr 10 '14 at 03:30
  • 1
    Note: you need "using System.Data.Entity.SqlServer;" not "using System.Data.Objects.SqlClient;" See http://stackoverflow.com/questions/19733085/linq-to-entities-does-not-recognize-the-method-system-string-stringconvertsyst – jgerman May 14 '16 at 03:06
  • Excellent! Yes, `LIKE == PATINDEX > 0` https://learn.microsoft.com/en-us/sql/t-sql/functions/patindex-transact-sql People really need to upvote this answer because almost every post has loads of accepted wrong answers (like this post does) and it is really hard to work out that this is the way to do it. – satnhak Apr 07 '17 at 06:41
  • 1
    @jgerman This would be the desired solution. However, now three years later, I don't think System.Data.Entity.SqlServer is available in Entity Framework Core 1.1. :/ – Bob Tabor Apr 10 '17 at 15:24
  • PATINDEX is also a lot slower that LIKE – Tod Feb 20 '18 at 09:39
3

Following on from Magnus' correct answer, here is an extension method that can be re-used, as I needed in my project.

public static class LinqExtensions
{
    public static Expression<Func<T, bool>> WildCardWhere<T>(this Expression<Func<T, bool>> source, Expression<Func<T, string>> selector, string terms, char separator)
    {
        if (terms == null || selector == null)
            return source;

        foreach (string term in terms.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries))
        {
            string current = term;
            source = source.And(
                Expression.Lambda<Func<T, bool>>(
                    Expression.Call(selector.Body, "Contains", null, Expression.Constant(current)),
                    selector.Parameters[0]
                )
            );
        }

        return source;
    }
}

Usage:

var terms = "%test%.doc%";
Expression<Func<Doc, bool>> whereClause = d => d;
whereClause = whereClause.WildCardWhere(d => d.docName, terms, '%');
whereClause = whereClause.WildCardWhere(d => d.someOtherProperty, "another%string%of%terms", '%');
var result = ListOfDocs.Where(whereClause).ToList();

The extension makes use of the predicate builder at http://petemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder/. The resulting sql does a single table scan of the table, no matter how many terms are in there. Jo Vdb has an example you could start from if you wanted an extension of iQueryable instead.

Community
  • 1
  • 1
  • Thanks @Legolomaniac, This is a really great solution, but I'm having trouble getting it to build... specifically on ` source = source.And( ` it tells me, **"No overload for method 'And' takes 1 arguments"** – m1m1k Apr 30 '13 at 16:34
2

You can try use this article, where author describes how to build a LIKE statement with wildcard characters in LINQ to Entities.

EDIT: Since the original link is now dead, here is the original extension class (as per Jon Koeter in the comments) and usage example.

Extension:

public static class LinqHelper
{
    //Support IQueryable (Linq to Entities)
    public static IQueryable<TSource> WhereLike<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, string>> valueSelector, string value, char wildcard)
    {
        return source.Where(BuildLikeExpression(valueSelector, value, wildcard));
    }

    //Support IEnumerable (Linq to objects)
    public static IEnumerable<TSource> WhereLike<TSource>(this IEnumerable<TSource> sequence, Func<TSource, string> expression, string value, char wildcard)
    {
        var regEx = WildcardToRegex(value, wildcard);

        //Prevent multiple enumeration:
        var arraySequence = sequence as TSource[] ?? sequence.ToArray();

        try
        {
            return arraySequence.Where(item => Regex.IsMatch(expression(item), regEx));
        }
        catch (ArgumentNullException)
        {
            return arraySequence;
        }
    }

    //Used for the IEnumerable support
    private static string WildcardToRegex(string value, char wildcard)
    {
        return "(?i:^" + Regex.Escape(value).Replace("\\" + wildcard, "." + wildcard) + "$)";
    }

    //Used for the IQueryable support
    private static Expression<Func<TElement, bool>> BuildLikeExpression<TElement>(Expression<Func<TElement, string>> valueSelector, string value, char wildcard)
    {
        if (valueSelector == null) throw new ArgumentNullException("valueSelector");

        var method = GetLikeMethod(value, wildcard);

        value = value.Trim(wildcard);
        var body = Expression.Call(valueSelector.Body, method, Expression.Constant(value));

        var parameter = valueSelector.Parameters.Single();
        return Expression.Lambda<Func<TElement, bool>>(body, parameter);
    }

    private static MethodInfo GetLikeMethod(string value, char wildcard)
    {
        var methodName = "Equals";

        var textLength = value.Length;
        value = value.TrimEnd(wildcard);
        if (textLength > value.Length)
        {
            methodName = "StartsWith";
            textLength = value.Length;
        }

        value = value.TrimStart(wildcard);
        if (textLength > value.Length)
        {
            methodName = (methodName == "StartsWith") ? "Contains" : "EndsWith";
        }

        var stringType = typeof(string);
        return stringType.GetMethod(methodName, new[] { stringType });
    }
}

Usage Example:

string strEmailToFind = "%@yahoo.com"

IQueryable<User> myUsers = entities.Users.WhereLike(u => u.EmailAddress, strEmailToFind, '%');

or, if you expect your users to be more accustomed to Windows Explorer-styled wildcards:

string strEmailToFind = "*@yahoo.com"

IQueryable<User> myUsers = entities.Users.WhereLike(u => u.EmailAddress, strEmailToFind, '*');
Community
  • 1
  • 1
EvgK
  • 1,907
  • 12
  • 10
  • I saw that earlier, it doesnt has an example to use it – ns12345 Feb 02 '11 at 19:52
  • Here is an example: entities.Table.WhereLike(el => el.position, position, '%'). So you should set what column your are searching for, pattern to search and wildcard character. But I've tested it and found it won't help you - it doesn't process wildcards which are not at start or end of the pattern, sorry. – EvgK Feb 02 '11 at 21:02
  • 2
    But how would you combine multiple Where clauses? For example, to generate "WHERE FirstName LIKE 'A%' OR LastName LIKE 'A%'"? – parleer Jan 09 '12 at 21:50
  • the "this article" link http://trentacular.com/2010/08/linq-to-entities-wild-card-like-extension-method/ is "not found" ... http://trentacular.com/ shows "trentacular.com is almost here! Upload your website to get started." – gerryLowry Oct 01 '16 at 01:24
  • 2
    Unfortunately the link is rot. This is why link only answers should be avoided – Tim Schmelter Oct 25 '16 at 08:22
  • The article seems to be down. You can view my answer, derived from the article, here: http://stackoverflow.com/questions/2542288/like-queries-in-entity-framework/29298128#29298128 – Jon Koeter Nov 08 '16 at 13:39
  • Why can't i do .WhereLike()? Its underlined red. Do i need to import any classes? – Nurul Mar 20 '17 at 07:04
  • This won't work for cases with no wildcard. In SQL Like is case insensitive, '=' can be either depending on configuration. If I wanted to do a whole word case insensitive search this does not translate to LIKE it substitues = in instead. – Tod Feb 20 '18 at 09:51
2

Use a regular expression...

The following will print out all of the files in the current directory that match test.doc* (dos wildcard style - which I believe is what you're asking for)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;

namespace RegexFileTester
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] _files = Directory.GetFiles(".");
            var _fileMatches =  from i in _files
                                where Regex.IsMatch(i, ".*test*.doc.*")
                                //where Regex.IsMatch(i, ".*cs")
                                select i;
            foreach(var _file in _fileMatches)
            {
                Console.WriteLine(_file);
            }
        }
    }
}
syllogism
  • 655
  • 1
  • 4
  • 12
  • No this is not required. you are getting all the files and then filtering this is unnecessary transfer of data. – ns12345 Feb 02 '11 at 19:59
  • What's the alternative? I've just done exactly that in my code, how can I select and query the filenames at the same time? – Ziv Feb 03 '11 at 00:13
  • Use Directory.EnumerateFiles(path, searchPattern) instead. (But I think he wants to query a DB) – Magnus Feb 03 '11 at 15:05
2

Split the String

var str =  "%test%.doc%";
var arr = str.Split(new[]{'%'} ,StringSplitOptions.RemoveEmptyEntries);
var q = tblUsers.Select (u => u);
foreach (var item in arr)
{
    var localItem = item;
    q = q.Where (x => x.userName.Contains(localItem));
}
Magnus
  • 45,362
  • 8
  • 80
  • 118
0

So I was trying the same thing - trying to pair down a List to return all candidates that matched a SearchTerm. I wanted it so that if a user typed "Arizona" it would return everything regardless of case that had Arizona. Also, if the user typed "Arizona Cha", it would return items like "Arizona License Change". The following worked:

private List<Certification> GetCertListBySearchString()
    {
        string[] searchTerms = SearchString.Split(' ');
        List<Certification> allCerts = _context.Certifications.ToList();

        allCerts = searchTerms.Aggregate(allCerts, (current, thisSearchString) => (from ac in current
                                                                                   where ac.Name.ToUpper().Contains(thisSearchString.ToUpper())
                                                                                   select ac).ToList());
          return allCerts;
    }
Paul Mc
  • 15
  • 2