Wie Operator im Entity Framework?

89

Wir versuchen, den Operator "LIKE" in Entity Framework für unsere Entitäten mit Zeichenfolgenfeldern zu implementieren, aber er scheint nicht unterstützt zu werden. Hat jemand anderes versucht, so etwas zu tun?

Dieser Blog-Beitrag fasst das Problem zusammen, das wir haben. Wir könnten Includes verwenden, aber das entspricht nur dem trivialsten Fall für LIKE. Das Kombinieren von enthält, beginnt mit, endet mit und indexof bringt uns dorthin, erfordert jedoch eine Übersetzung zwischen Standard-Platzhaltern und Linq to Entities-Code.

brien
quelle
1
Gehen Sie zu dieser Antwort, wenn Sie EF 6.2.x bereits verwenden. Auf diese Antwort, wenn Sie EF Core 2.x
CodeNotFound

Antworten:

34

Dies ist jetzt ein alter Beitrag, aber für alle, die nach einer Antwort suchen, sollte dieser Link helfen. Gehen Sie zu dieser Antwort, wenn Sie EF 6.2.x bereits verwenden. Auf diese Antwort, wenn Sie EF Core 2.x verwenden

Kurzfassung:

SqlFunctions.PatIndex Methode - die Startposition des ersten Auftretens eines Musters in einem angegebenen Ausdruck oder Nullen, wenn das Muster nicht gefunden wird, für alle gültigen Text- und Zeichendatentypen zurück

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

Eine kleine Erklärung erscheint auch in diesem Forenthread .

Yann Duran
quelle
59
Wie ist die akzeptierte Antwort diejenige, die auf ein MSDN-Forum verweist, das auf diese Frage und die unten stehende Antwort verweist ?
Eonasdan
Die Antwort war die Verwendung der SqlFunctions.PatIndex-Methode. Der verlinkte Forenthread sollte ein bisschen mehr "Hintergrund" -Informationen liefern.
Yann Duran
Die Antwort unten ist gut für einfache Muster, aber wenn ich "WHERE Name LIKE 'abc [0-9]%'" oder ein anderes komplexeres Muster sagen möchte, ist die einfache Verwendung von Contains () nicht ganz ausreichend.
HotN
1
Dup dieser älteren Antwort auf diese Frage. (Nicht von seinem ersten Teil, sondern von seiner alternativen Lösung.)
Frédéric
154

Ich weiß eigentlich nichts über EF, aber in LINQ to SQL drücken Sie normalerweise eine LIKE-Klausel mit String.Contains aus:

where entity.Name.Contains("xyz")

wird übersetzt in

WHERE Name LIKE '%xyz%'

(Verwenden Sie StartsWithund EndsWithfür anderes Verhalten.)

Ich bin mir nicht ganz sicher, ob das hilfreich ist, weil ich nicht verstehe, was Sie meinen, wenn Sie sagen, dass Sie versuchen, LIKE zu implementieren . Wenn ich völlig falsch verstanden habe, lass es mich wissen und ich werde diese Antwort löschen :)

Jon Skeet
quelle
4
Bitte beachten Sie, dass "WHERE Name LIKE '% xyz%'" keinen Index verwenden kann. Wenn die Tabelle also riesig ist,
Mitch Wheat
1
Nun, wir möchten in der Lage sein, auf bla * bla foo bar foo? Bar? Foo bar? und andere komplexe Muster. Unser aktueller Ansatz ähnelt dem, was Sie erwähnt haben. Wir würden diese Abfragen mithilfe von "enthält", "indexof", "Start", "End" usw. in Operationen umwandeln. Ich hatte nur gehofft, dass es eine allgemeinere Lösung gibt.
Brien
2
Nicht, dass mir das bewusst wäre - ich vermute, dass komplexe Muster db-spezifischer und allgemein schwer auszudrücken sind.
Jon Skeet
4
@ Jon Skeet: Meines Wissens entspricht die LIKE-Funktionalität dem ANSI-Standard und ist in SQL Server, Oracle und DB2 ziemlich gleich.
AK
2
Eine Sache, die ich bei der Verwendung dieser Operatoren und von MS SQL gesehen habe, ist, dass EF sie als Escape-Parameter "Name LIKE @ p__linq__1 ESCAPE N '' ~ ''" hinzufügt, was in meinem sehr eingeschränkten Anwendungsfall viel langsamer ist als wenn die Suchzeichenfolge ist nur in der Abfrage "Name wie '% xyz%'. Für die Szenarien, die ich habe, verwende ich immer noch StartsWith und Contains, aber ich mache es über dynamische Linq, weil das den Parameter in die SQL-Anweisung einfügt, die in meinem Szenario eine erzeugt Effizientere Abfrage. Nicht sicher, ob dies eine EF 4.0-Sache ist oder nicht. Sie können auch ObjectQueryParameters verwenden, um dasselbe zu erreichen ...
Shane Neuville
34

Ich hatte das gleiche Problem.

Im Moment habe ich mich für die clientseitige Wildcard / Regex-Filterung entschieden, die auf http://www.codeproject.com/Articles/11556/Converting-Wildcards-to-Regexes?msg=1423024#xx1423024xx basiert - es ist einfach und funktioniert so erwartet.

Ich habe eine weitere Diskussion zu diesem Thema gefunden: http://forums.asp.net/t/1654093.aspx/2/10
Dieser Beitrag sieht vielversprechend aus, wenn Sie Entity Framework> = 4.0 verwenden:

Verwenden Sie SqlFunctions.PatIndex:

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

So was:

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

Hinweis: Diese Lösung ist nur für SQL Server vorgesehen, da nicht standardmäßige PATINDEX-Funktionen verwendet werden.

surfen
quelle
Während PatIndex "funktioniert", wird es zurückkommen, um Sie zu beißen. PatIndex in der where-Klausel verwendet nicht die Indizes für die Spalte, nach der Sie filtern möchten.
BlackICE
@BlackICE das wird erwartet. Wenn Sie nach innerem Text suchen (% CD% BLUE%), kann der Server keine Indizes verwenden. Wann immer möglich, ist die Suche nach Text von Anfang an (CD% BLUE%) effizienter.
Surfen
@surfen patindex ist schlechter als das, es wird den Index auch ohne% nicht verwenden, bei der Suche nach (BLAUE CD%) mit patindex wird der Spaltenindex nicht verwendet.
BlackICE
20

Update: In EF 6.2 gibt es einen ähnlichen Operator

Where(i => DbFunctions.Like(searchstring ,like expression)
Lode Vlaeminck
quelle
Wäre das nicht ein klareres Beispiel : Where(obj => DbFunctions.Like(obj.Column , "%expression%")?
DCD
19

Es wird ein LIKEOperator hinzugefügt in Entity Framework Core 2.0:

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

Im Vergleich dazu wird ... where e.Title.Contains("developer") ...es SQL LIKEeher übersetzt als CHARINDEXwir es für eine ContainsMethode sehen.

Dmitry Pavlov
quelle
5

Es wird in der Dokumentation als Teil von Entity SQL ausdrücklich erwähnt. Erhalten Sie eine Fehlermeldung?

// 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
quelle
1
Ich wäre versucht, mich von Entity SQL fernzuhalten, falls Sie sich in Zukunft von EF entfernen möchten. Gehen Sie auf Nummer sicher und bleiben Sie stattdessen bei den Optionen Contains (), StartsWith () und EndsWith () in der ursprünglichen Antwort.
Stephen Newman
1
Das lässt sich gut kompilieren, schlägt aber zur Laufzeit fehl.
Brien
Der Code, den ich gepostet habe, schlägt zur Laufzeit fehl? Es kommt vom Microsoft-Link.
Robert Harvey
Ich habe die Frage mit einem Link zu einem Blog-Beitrag bearbeitet, der das gleiche Problem beschreibt, das wir haben.
Brien
Sieht aus wie Contains () ist Ihr Ticket. Aber wie Jon Skeet betonte, müssen Sie möglicherweise auf ein tatsächliches SQL zurückgreifen, das die Datenbank direkt manipuliert, wenn Contains Ihren Anforderungen nicht entspricht.
Robert Harvey
2

Wenn Sie MS SQL verwenden, habe ich zwei Erweiterungsmethoden geschrieben, um das% -Zeichen für die Platzhaltersuche zu unterstützen. (LinqKit ist erforderlich)

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

Verwendung

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 und es sollte übersetzt werden

....
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
quelle
danke für deinen Kommentar Ronel, kann ich irgendetwas helfen? Was ist die Fehlermeldung?
Steven Chong
2

Für EfCore ist hier ein Beispiel zum Erstellen eines LIKE-Ausdrucks

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

Sie können ganz einfach ein echtes Like in Link to Entities verwenden

Hinzufügen

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

zu Ihrem EDMX in diesem Tag:

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

Denken Sie auch an den Namespace im <schema namespace="" />Attribut

Fügen Sie dann eine Erweiterungsklasse in den obigen Namespace ein:

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

Diese Erweiterungsmethode wird nun der EDMX-Funktion zugeordnet.

Weitere Informationen hier: http://jendaperl.blogspot.be/2011/02/like-in-linq-to-entities.html

brechtvhb
quelle