116

We're trying to implement the "LIKE" operator in Entity Framework for our entities with string fields, but it doesn't appear to be supported. Has anyone else tried to do something like this?

This blog post summarizes the issue we're having. We could use contains, but that only matches the most trivial case for LIKE. Combining contains, startswith, endswith, and indexof gets us there, but requires a translation between standard wildcards and Linq to Entities code.

brien
  • 4,400
  • 4
  • 30
  • 32
  • 3
    Go to [this answer](https://stackoverflow.com/a/47442276/797882) if you are already using EF 6.2.x. To [this answer](https://stackoverflow.com/a/46489164/797882) if you're using EF Core 2.x – CodeNotFound Jun 14 '18 at 08:41

9 Answers9

178

I don't know anything about EF really, but in LINQ to SQL you usually express a LIKE clause using String.Contains:

where entity.Name.Contains("xyz")

translates to

WHERE Name LIKE '%xyz%'

(Use StartsWith and EndsWith for other behaviour.)

I'm not entirely sure whether that's helpful, because I don't understand what you mean when you say you're trying to implement LIKE. If I've misunderstood completely, let me know and I'll delete this answer :)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 8
    please note that "WHERE Name LIKE '%xyz%'" will be unable to use an index, so if table is huge it might not perform that well... – Mitch Wheat Jun 23 '09 at 14:49
  • 2
    Well, we'd like to be able to match on *blah *blah* foo*bar foo?bar ?foo*bar? and other complex patterns. Our current approach is similar to what you mentioned, we would convert those queries into operations using contains, indexof, startswith, endswith, etc. I was just hoping that there was a more general-purpose solution. – brien Jun 23 '09 at 14:50
  • 2
    Not that I'm aware of - I suspect that complex patterns end up being more db-specific, and hard to express in a general way. – Jon Skeet Jun 23 '09 at 14:52
  • 5
    @Jon Skeet: to my best knowledge LIKE functionality is in ANSI standard and it is pretty much the same in SQL Server, Oracle, and DB2. – A-K Jun 23 '09 at 16:02
  • @AlexKuznetsov: I seem to remember having some issues with compatibility between SQL Server and Oracle in the past, particularly when it comes to escaping. I had to write a Hibernate operator to cope with % being in the query string, and the Oracle and SQL Server implementations were different. It could be that I was missing a trick, or that the world has improved since then. – Jon Skeet Jun 23 '09 at 16:07
  • +1 I just tried this out on EF 3.5 on SQL Server, and it works at least in that combination. – Mark Seemann Mar 19 '10 at 16:14
  • 2
    One thing I've seen with using these operators and MS SQL is that EF adds them as escaped parameters "Name LIKE @p__linq__1 ESCAPE N''~''" which in my very limited use case performs a lot slower then if the search string is just in the query "Name like '%xyz%'. For the scenarios I have I'm still using StartsWith and Contains but I do it via dynamic linq because that injects the parameter into the SQL statement which in my scenario is producing a more efficient query. Not sure if this is an EF 4.0 thing or not. You can also use ObjectQueryParameters to achieve the same thing... – Shane Neuville Aug 17 '12 at 17:58
  • Yes, this is the solution for SQL LIKE equivalent: .Contains(value) || .StartsWith(value) || .EndsWith(value) – landsteven Jun 30 '16 at 17:04
  • 1
    @landsteven: When would you expect `StartsWith` or `EndsWith` to return `true`, but `Contains` to return false? – Jon Skeet Jun 30 '16 at 17:07
  • @John Skeet: When my user inputs "ABC Dealer" as the string to query my DB in order to return results for an AutoCompleteBox.ItemsSource. The results need to include a listing of all relative records found from the source table even when the actual record they are looking for may start OR contain OR end with the input provided. – landsteven Jun 30 '16 at 17:54
  • 1
    @landsteven: That doesn't answer my question. Surely if the record value starts with the user input, it also *contains* the user input. So "ABC Dealer X" contains "ABC Dealer", as does "X ABC Dealer". Can you given an example where Contains would return false, but StartsWith or EndsWith would return true? – Jon Skeet Jun 30 '16 at 18:43
50

Update: In EF 6.2 there is a like operator

Where(obj => DbFunctions.Like(obj.Column , "%expression%"))
Chris Missal
  • 5,987
  • 3
  • 28
  • 46
Lode Vlaeminck
  • 894
  • 1
  • 9
  • 24
45

There is LIKE operator is added in Entity Framework Core 2.0:

var query = from e in _context.Employees
                    where EF.Functions.Like(e.Title, "%developer%")
                    select e;

Comparing to ... where e.Title.Contains("developer") ... it is really translated to SQL LIKE rather than CHARINDEX we see for Contains method.

Dmitry Pavlov
  • 30,789
  • 8
  • 97
  • 121
44

This is an old post now, but for anyone looking for the answer, this link should help. Go to this answer if you are already using EF 6.2.x. To this answer if you're using EF Core 2.x

Short version:

SqlFunctions.PatIndex method - returns the starting position of the first occurrence of a pattern in a specified expression, or zeros if the pattern is not found, on all valid text and character data types

Namespace: System.Data.Objects.SqlClient Assembly: System.Data.Entity (in System.Data.Entity.dll)

A bit of an explanation also appears in this forum thread.

Henry Woody
  • 14,024
  • 7
  • 39
  • 56
Yann Duran
  • 3,842
  • 1
  • 24
  • 24
  • 65
    how is the accepted answer the one that links to an MSDN forum that links back to this question to the [answer below](http://stackoverflow.com/a/9122312/237917)? – Eonasdan Oct 17 '12 at 13:27
  • The answer was to use the SqlFunctions.PatIndex method. The linked forum thread was to provide a bit more "background" info. – Yann Duran May 17 '14 at 00:20
  • The answer below is nice for simple patterns, but if I want to say "WHERE Name LIKE 'abc[0-9]%'" or some other more complex pattern, simply using Contains() doesn't quite cut it. – HotN Nov 24 '14 at 21:18
  • 1
    Dup of this older [answer](http://stackoverflow.com/a/9122312/1178314) to this question. (Not of its first part, but of its alternate solution.) – Frédéric Aug 05 '16 at 15:46
38

I had the same problem.

For now, I've settled with client-side Wildcard/Regex filtering based on http://www.codeproject.com/Articles/11556/Converting-Wildcards-to-Regexes?msg=1423024#xx1423024xx - it's simple and works as expected.

I've found another discussion on this topic: http://forums.asp.net/t/1654093.aspx/2/10
This post looks promising if you use Entity Framework >= 4.0:

Use SqlFunctions.PatIndex:

http://msdn.microsoft.com/en-us/library/system.data.objects.sqlclient.sqlfunctions.patindex.aspx

Like this:

var q = EFContext.Products.Where(x =>
SqlFunctions.PatIndex("%CD%BLUE%", x.ProductName) > 0);

Note: this solution is for SQL-Server only, because it uses non-standard PATINDEX function.

Community
  • 1
  • 1
surfen
  • 4,644
  • 3
  • 34
  • 46
  • While PatIndex "works", it will come back to bite you, PatIndex in the where clause does not use the indexes on the column you would like to filter on. – BlackICE Jan 22 '14 at 21:39
  • @BlackICE this is expected. When you search on inner text (%CD%BLUE%) the server won't be able to use indexes. Whenever possible, searching text from the begining (CD%BLUE%) is more efficient. – surfen Jan 23 '14 at 07:46
  • @surfen patindex is worse than that though, it won't use the index even without % in front, searching for (BLUE CD%) with patindex won't use the column index. – BlackICE Jan 23 '14 at 13:34
5

It is specifically mentioned in the documentation as part of Entity SQL. Are you getting an error message?

// LIKE and ESCAPE
// If an AdventureWorksEntities.Product contained a Name 
// with the value 'Down_Tube', the following query would find that 
// value.
Select value P.Name FROM AdventureWorksEntities.Product 
    as P where P.Name LIKE 'DownA_%' ESCAPE 'A'

// LIKE
Select value P.Name FROM AdventureWorksEntities.Product 
    as P where P.Name like 'BB%'

http://msdn.microsoft.com/en-us/library/bb399359.aspx

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
  • 1
    I would be tempted to stay away from Entity SQL in case you wanted to move away from EF in the future. Play it safe and stick with the Contains(), StartsWith() and EndsWith() options in the original response instead. – Stephen Newman Jun 23 '09 at 15:01
  • 1
    That compiles fine, but fails at runtime. – brien Jun 23 '09 at 15:02
  • The code I posted fails at runtime? It comes from the Microsoft link. – Robert Harvey Jun 23 '09 at 15:12
  • I edited the question with a link to a blog post describing the same issue we're having. – brien Jun 23 '09 at 15:19
  • Looks like Contains() is your ticket. But as Jon Skeet pointed out, you may have to drop down to some actual SQL manipulating the database directly, if Contains doesn't meet your needs. – Robert Harvey Jun 23 '09 at 15:24
  • Another relevant StackOverflow post: http://stackoverflow.com/questions/835790/how-to-do-sql-like-in-linq – Robert Harvey Jun 23 '09 at 15:24
5

For EfCore here is a sample to build LIKE expression

protected override Expression<Func<YourEntiry, bool>> BuildLikeExpression(string searchText)
    {
        var likeSearch = $"%{searchText}%";

        return t => EF.Functions.Like(t.Code, likeSearch)
                    || EF.Functions.Like(t.FirstName, likeSearch)
                    || EF.Functions.Like(t.LastName, likeSearch);
    }

//Calling method

var query = dbContext.Set<YourEntity>().Where(BuildLikeExpression("Text"));
Duy Hoang
  • 109
  • 1
  • 6
2

if you're using MS Sql, I have wrote 2 extension methods to support the % character for wildcard search. (LinqKit is required)

public static class ExpressionExtension
{
    public static Expression<Func<T, bool>> Like<T>(Expression<Func<T, string>> expr, string likeValue)
    {
        var paramExpr = expr.Parameters.First();
        var memExpr = expr.Body;

        if (likeValue == null || likeValue.Contains('%') != true)
        {
            Expression<Func<string>> valExpr = () => likeValue;
            var eqExpr = Expression.Equal(memExpr, valExpr.Body);
            return Expression.Lambda<Func<T, bool>>(eqExpr, paramExpr);
        }

        if (likeValue.Replace("%", string.Empty).Length == 0)
        {
            return PredicateBuilder.True<T>();
        }

        likeValue = Regex.Replace(likeValue, "%+", "%");

        if (likeValue.Length > 2 && likeValue.Substring(1, likeValue.Length - 2).Contains('%'))
        {
            likeValue = likeValue.Replace("[", "[[]").Replace("_", "[_]");
            Expression<Func<string>> valExpr = () => likeValue;
            var patExpr = Expression.Call(typeof(SqlFunctions).GetMethod("PatIndex",
                new[] { typeof(string), typeof(string) }), valExpr.Body, memExpr);
            var neExpr = Expression.NotEqual(patExpr, Expression.Convert(Expression.Constant(0), typeof(int?)));
            return Expression.Lambda<Func<T, bool>>(neExpr, paramExpr);
        }

        if (likeValue.StartsWith("%"))
        {
            if (likeValue.EndsWith("%") == true)
            {
                likeValue = likeValue.Substring(1, likeValue.Length - 2);
                Expression<Func<string>> valExpr = () => likeValue;
                var containsExpr = Expression.Call(memExpr, typeof(String).GetMethod("Contains",
                    new[] { typeof(string) }), valExpr.Body);
                return Expression.Lambda<Func<T, bool>>(containsExpr, paramExpr);
            }
            else
            {
                likeValue = likeValue.Substring(1);
                Expression<Func<string>> valExpr = () => likeValue;
                var endsExpr = Expression.Call(memExpr, typeof(String).GetMethod("EndsWith",
                    new[] { typeof(string) }), valExpr.Body);
                return Expression.Lambda<Func<T, bool>>(endsExpr, paramExpr);
            }
        }
        else
        {
            likeValue = likeValue.Remove(likeValue.Length - 1);
            Expression<Func<string>> valExpr = () => likeValue;
            var startsExpr = Expression.Call(memExpr, typeof(String).GetMethod("StartsWith",
                new[] { typeof(string) }), valExpr.Body);
            return Expression.Lambda<Func<T, bool>>(startsExpr, paramExpr);
        }
    }

    public static Expression<Func<T, bool>> AndLike<T>(this Expression<Func<T, bool>> predicate, Expression<Func<T, string>> expr, string likeValue)
    {
        var andPredicate = Like(expr, likeValue);
        if (andPredicate != null)
        {
            predicate = predicate.And(andPredicate.Expand());
        }
        return predicate;
    }

    public static Expression<Func<T, bool>> OrLike<T>(this Expression<Func<T, bool>> predicate, Expression<Func<T, string>> expr, string likeValue)
    {
        var orPredicate = Like(expr, likeValue);
        if (orPredicate != null)
        {
            predicate = predicate.Or(orPredicate.Expand());
        }
        return predicate;
    }
}

usage

var orPredicate = PredicateBuilder.False<People>();
orPredicate = orPredicate.OrLike(per => per.Name, "He%llo%");
orPredicate = orPredicate.OrLike(per => per.Name, "%Hi%");

var predicate = PredicateBuilder.True<People>();
predicate = predicate.And(orPredicate.Expand());
predicate = predicate.AndLike(per => per.Status, "%Active");

var list = dbContext.Set<People>().Where(predicate.Expand()).ToList();    

in ef6 and it should translate to

....
from People per
where (
    patindex(@p__linq__0, per.Name) <> 0
    or per.Name like @p__linq__1 escape '~'
) and per.Status like @p__linq__2 escape '~'

', @p__linq__0 = '%He%llo%', @p__linq__1 = '%Hi%', @p__linq_2 = '%Active'

teamchong
  • 1,326
  • 3
  • 16
  • 13
0

You can use a real like in Link to Entities quite easily

Add

    <Function Name="String_Like" ReturnType="Edm.Boolean">
      <Parameter Name="searchingIn" Type="Edm.String" />
      <Parameter Name="lookingFor" Type="Edm.String" />
      <DefiningExpression>
        searchingIn LIKE lookingFor
      </DefiningExpression>
    </Function>

to your EDMX in this tag:

edmx:Edmx/edmx:Runtime/edmx:ConceptualModels/Schema

Also remember the namespace in the <schema namespace="" /> attribute

Then add an extension class in the above namespace:

public static class Extensions
{
    [EdmFunction("DocTrails3.Net.Database.Models", "String_Like")]
    public static Boolean Like(this String searchingIn, String lookingFor)
    {
        throw new Exception("Not implemented");
    }
}

This extension method will now map to the EDMX function.

More info here: http://jendaperl.blogspot.be/2011/02/like-in-linq-to-entities.html

brechtvhb
  • 1,029
  • 2
  • 13
  • 26