Podobnie jak operator w Entity Framework?

93

Próbujemy zaimplementować operator „LIKE” w Entity Framework dla naszych jednostek z polami ciągów, ale wygląda na to, że nie jest obsługiwany. Czy ktoś inny próbował zrobić coś takiego?

W tym poście na blogu podsumowano występujący problem. Moglibyśmy użyć zawiera, ale to pasuje tylko do najbardziej trywialnego przypadku LIKE. Łączenie zawiera, zaczyna się, kończy się i indeksuje, ale wymaga tłumaczenia między standardowymi symbolami wieloznacznymi a kodem Linq na kod Entities.

brien
źródło
1
Przejdź do tej odpowiedzi, jeśli korzystasz już z EF 6.2.x. Do tej odpowiedzi, jeśli używasz EF Core 2.x
CodeNotFound,

Odpowiedzi:

36

To jest teraz stary post, ale każdemu, kto szuka odpowiedzi, ten link powinien pomóc. Przejdź do tej odpowiedzi, jeśli używasz już EF 6.2.x. Do tej odpowiedzi, jeśli używasz EF Core 2.x

Krótka wersja:

Metoda SqlFunctions.PatIndex - zwraca pozycję początkową pierwszego wystąpienia wzorca w określonym wyrażeniu lub zer, jeśli wzorzec nie zostanie znaleziony, we wszystkich poprawnych typach danych tekstowych i znakowych

Przestrzeń nazw: System.Data.Objects.SqlClient Assembly: System.Data.Entity (w System.Data.Entity.dll)

W tym wątku na forum pojawia się też trochę wyjaśnienia .

Yann Duran
źródło
59
w jaki sposób akceptowana jest odpowiedź zawierająca łącze do forum MSDN, które zawiera odsyłacz do tego pytania do odpowiedzi poniżej ?
Eonasdan
Odpowiedzią było użycie metody SqlFunctions.PatIndex. Wątek na forum, do którego prowadzi link, miał dostarczyć nieco więcej informacji w tle.
Yann Duran
Poniższa odpowiedź jest dobra w przypadku prostych wzorców, ale jeśli chcę powiedzieć „WHERE Name LIKE 'abc [0-9]%'” lub inny bardziej złożony wzorzec, po prostu użycie Contains () nie wystarcza.
HotN
1
Duplikat tej starszej odpowiedzi na to pytanie. (Nie z pierwszej części, ale z alternatywnego rozwiązania.)
Frédéric
154

Tak naprawdę nie wiem nic o EF, ale w LINQ to SQL zwykle wyrażasz klauzulę LIKE za pomocą String.

where entity.Name.Contains("xyz")

przetłumaczyć na

WHERE Name LIKE '%xyz%'

(Użyj StartsWithi EndsWithdo innego zachowania.)

Nie jestem do końca pewien, czy to jest pomocne, ponieważ nie rozumiem, co masz na myśli, mówiąc, że próbujesz wdrożyć LIKE. Jeśli kompletnie źle zrozumiałem, daj mi znać, a usunę tę odpowiedź :)

Jon Skeet
źródło
4
pamiętaj, że „WHERE Name LIKE '% xyz%'” nie będzie w stanie użyć indeksu, więc jeśli tabela jest ogromna, może nie działać tak dobrze ...
Mitch Wheat
1
Cóż, chcielibyśmy być w stanie dopasować bla * bla foo bar foo? Bar? Foo bar? i inne złożone wzorce. Nasze obecne podejście jest podobne do tego, o czym wspomniałeś, konwertowalibyśmy te zapytania na operacje przy użyciu funkcji include, indexof, startedwith, endwith itd. Miałem tylko nadzieję, że istnieje rozwiązanie bardziej ogólnego przeznaczenia.
brien
2
Nie żebym był tego świadomy - podejrzewam, że złożone wzorce są bardziej specyficzne dla db i trudne do wyrażenia w ogólny sposób.
Jon Skeet
4
@Jon Skeet: według mojej najlepszej wiedzy funkcjonalność LIKE jest w standardzie ANSI i jest prawie taka sama w SQL Server, Oracle i DB2.
AK
2
Jedną z rzeczy, które widziałem przy używaniu tych operatorów i MS SQL, jest to, że EF dodaje je jako parametry ze zmianą znaczenia "Name LIKE @ p__linq__1 ESCAPE N '' ~ ''", co w moim bardzo ograniczonym przypadku użycia działa dużo wolniej niż jeśli szukany ciąg znajduje się w zapytaniu „Nazwa taka jak„% xyz% ”. W przypadku scenariuszy, które mam, nadal używam StartsWith i Contains, ale robię to za pośrednictwem dynamicznego linq, ponieważ wstrzykuje parametr do instrukcji SQL, która w moim scenariuszu generuje . bardziej efektywne zapytania nie wiem, czy jest to sprawa EF 4.0 lub nie można również użyć ObjectQueryParameters aby osiągnąć to samo ....
Shane Neuville
35

Miałem ten sam problem.

Na razie zdecydowałem się na filtrowanie Wildcard / Regex po stronie klienta w oparciu o http://www.codeproject.com/Articles/11556/Converting-Wildcards-to-Regexes?msg=1423024#xx1423024xx - to proste i działa jak spodziewany.

Znalazłem inną dyskusję na ten temat: http://forums.asp.net/t/1654093.aspx/2/10
Ten post wygląda obiecująco, jeśli używasz Entity Framework> = 4.0:

Użyj SqlFunctions.PatIndex:

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

Lubię to:

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

Uwaga: to rozwiązanie jest przeznaczone tylko dla SQL-Server, ponieważ używa niestandardowej funkcji PATINDEX.

surfować
źródło
Podczas gdy PatIndex "działa", wróci, by cię ugryźć, PatIndex w klauzuli where nie używa indeksów w kolumnie, według której chcesz filtrować.
BlackICE
@BlackICE to jest oczekiwane. Podczas wyszukiwania w tekście wewnętrznym (% CD% BLUE%) serwer nie będzie mógł używać indeksów. Jeśli to możliwe, wyszukiwanie tekstu od początku (CD% BLUE%) jest bardziej wydajne.
surfuj
@surfen patindex jest jednak gorszy, nie użyje indeksu nawet bez znaku% na początku, wyszukiwanie (BLUE CD%) z patindex nie użyje indeksu kolumny.
BlackICE
23

Aktualizacja: W EF 6.2 jest operator Like

Where(obj => DbFunctions.Like(obj.Column , "%expression%")
Lode Vlaeminck
źródło
Czy nie byłoby to wyraźniejszy przykład: Where(obj => DbFunctions.Like(obj.Column , "%expression%")?
DCD
Tak jest. Zmieniłem to
Lode Vlaeminck
20

Istnieje LIKEoperatora dodaje się Entity Framework Core 2.0:

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

W porównaniu do ... where e.Title.Contains("developer") ...tego jest naprawdę przetłumaczone na, SQL LIKEa CHARINDEXnie widzimy Containsmetody.

Dmitrij Pawłow
źródło
5

Jest to wyraźnie wymienione w dokumentacji jako część Entity SQL. Czy otrzymujesz komunikat o błędzie?

// 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
źródło
1
Kusi mnie, aby trzymać się z daleka od Entity SQL na wypadek, gdybyś chciał odejść od EF w przyszłości. Graj bezpiecznie i trzymaj się zamiast tego opcji Contains (), StartsWith () i EndsWith () w oryginalnej odpowiedzi.
Stephen Newman
1
Kompiluje się dobrze, ale kończy się niepowodzeniem w czasie wykonywania.
brien
Kod, który opublikowałem, nie działa w czasie wykonywania? Pochodzi z linku Microsoft.
Robert Harvey
Zmieniłem pytanie z linkiem do wpisu na blogu opisującego ten sam problem, który mamy.
brien
Wygląda na to, że Twój bilet jest Contains (). Ale jak zauważył Jon Skeet, być może będziesz musiał przejść do rzeczywistego manipulowania bazą danych bezpośrednio w SQL, jeśli Contains nie spełnia twoich potrzeb.
Robert Harvey
2

jeśli używasz MS Sql, napisałem 2 metody rozszerzające, które obsługują znak% dla wyszukiwania wieloznacznego. (Wymagany jest LinqKit)

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;
    }
}

stosowanie

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();    

w ef6 i powinno się to przekładać na

....
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 '

Steven Chong
źródło
dzięki za komentarz Ronel, czy mogę w czymś pomóc? jaki jest komunikat o błędzie?
Steven Chong
2

Dla EfCore tutaj jest przykład do zbudowania wyrażenia LIKE

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
źródło
0

Możesz dość łatwo użyć prawdziwego polubienia w Link to Entities

Dodaj

    <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>

do twojego EDMX w tym tagu:

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

Pamiętaj również o przestrzeni nazw w <schema namespace="" />atrybucie

Następnie dodaj klasę rozszerzenia w powyższej przestrzeni nazw:

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");
    }
}

Ta metoda rozszerzenia będzie teraz mapowana na funkcję EDMX.

Więcej informacji tutaj: http://jendaperl.blogspot.be/2011/02/like-in-linq-to-entities.html

brechtvhb
źródło