Comme opérateur dans Entity Framework?

91

Nous essayons d'implémenter l'opérateur "LIKE" dans Entity Framework pour nos entités avec des champs de chaîne, mais il ne semble pas être pris en charge. Quelqu'un d'autre a-t-il essayé de faire quelque chose comme ça?

Cet article de blog résume le problème que nous rencontrons. Nous pourrions utiliser contient, mais cela ne correspond qu'au cas le plus trivial de LIKE. Combiner contient, commence par, se termine par et indexof nous y conduit, mais nécessite une traduction entre les caractères génériques standard et le code Linq en entités.

Brien
la source
1
Accédez à cette réponse si vous utilisez déjà EF 6.2.x. À cette réponse si vous utilisez EF Core 2.x
CodeNotFound

Réponses:

35

Ceci est un ancien article maintenant, mais pour quiconque cherche la réponse, ce lien devrait aider. Accédez à cette réponse si vous utilisez déjà EF 6.2.x. À cette réponse si vous utilisez EF Core 2.x

Version courte:

Méthode SqlFunctions.PatIndex - renvoie la position de départ de la première occurrence d'un modèle dans une expression spécifiée, ou des zéros si le modèle n'est pas trouvé, sur tous les types de données texte et caractère valides

Espace de noms: System.Data.Objects.SqlClient Assembly: System.Data.Entity (dans System.Data.Entity.dll)

Une petite explication apparaît également dans ce fil de discussion .

Yann Duran
la source
59
comment la réponse acceptée est-elle celle qui renvoie à un forum MSDN qui renvoie à cette question à la réponse ci - dessous ?
Eonasdan
La réponse était d'utiliser la méthode SqlFunctions.PatIndex. Le fil de discussion lié au forum était de fournir un peu plus d'informations "de fond".
Yann Duran
La réponse ci-dessous est intéressante pour les modèles simples, mais si je veux dire "WHERE Name LIKE 'abc [0-9]%'" ou un autre modèle plus complexe, simplement utiliser Contains () ne le coupe pas tout à fait.
HotN
1
Dup de cette réponse plus ancienne à cette question. (Pas de sa première partie, mais de sa solution alternative.)
Frédéric
154

Je ne sais vraiment rien sur EF, mais dans LINQ to SQL, vous exprimez généralement une clause LIKE en utilisant String.

where entity.Name.Contains("xyz")

Se traduit par

WHERE Name LIKE '%xyz%'

(Utilisez StartsWithet EndsWithpour un autre comportement.)

Je ne suis pas tout à fait sûr que cela soit utile, car je ne comprends pas ce que vous voulez dire quand vous dites que vous essayez d' implémenter LIKE. Si j'ai complètement mal compris, faites-le moi savoir et je supprimerai cette réponse :)

Jon Skeet
la source
4
veuillez noter que "WHERE Name LIKE '% xyz%'" ne pourra pas utiliser d'index, donc si la table est énorme, elle risque de ne pas fonctionner aussi bien ...
Mitch Wheat
1
Eh bien, nous aimerions pouvoir correspondre sur blah * blah foo bar foo? Bar? Foo bar? et d'autres modèles complexes. Notre approche actuelle est similaire à ce que vous avez mentionné, nous convertirions ces requêtes en opérations en utilisant contains, indexof, startswith, endswith, etc. J'espérais juste qu'il y aurait une solution plus générale.
brien
2
Non pas que je sache - je soupçonne que les modèles complexes finissent par être plus spécifiques à la base de données et difficiles à exprimer d'une manière générale.
Jon Skeet
4
@Jon Skeet: à ma connaissance, la fonctionnalité LIKE est dans la norme ANSI et c'est à peu près la même chose dans SQL Server, Oracle et DB2.
AK
2
Une chose que j'ai vue avec l'utilisation de ces opérateurs et de MS SQL est que EF les ajoute en tant que paramètres échappés "Nom LIKE @ p__linq__1 ESCAPE N '' ~ ''" qui, dans mon cas d'utilisation très limité, est beaucoup plus lent que si la chaîne de recherche est juste dans la requête "Nom comme '% xyz%'. Pour les scénarios que j'ai, j'utilise toujours StartsWith et Contains mais je le fais via linq dynamique car cela injecte le paramètre dans l'instruction SQL qui, dans mon scénario, produit un requête plus efficace. Je ne sais pas si c'est une chose EF 4.0 ou non. Vous pouvez également utiliser ObjectQueryParameters pour obtenir la même chose ...
Shane Neuville
35

J'ai eu le même problème.

Pour l'instant, j'ai opté pour le filtrage Wildcard / Regex côté client basé sur http://www.codeproject.com/Articles/11556/Converting-Wildcards-to-Regexes?msg=1423024#xx1423024xx - c'est simple et fonctionne comme attendu.

J'ai trouvé une autre discussion sur ce sujet: http://forums.asp.net/t/1654093.aspx/2/10
Cet article semble prometteur si vous utilisez Entity Framework> = 4.0:

Utilisez SqlFunctions.PatIndex:

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

Comme ça:

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

Remarque: cette solution est réservée à SQL-Server, car elle utilise la fonction PATINDEX non standard.

surfen
la source
Pendant que PatIndex "fonctionne", il reviendra pour vous mordre, PatIndex dans la clause where n'utilise pas les index de la colonne sur laquelle vous souhaitez filtrer.
BlackICE
@BlackICE c'est attendu. Lorsque vous effectuez une recherche sur du texte interne (% CD% BLUE%), le serveur ne pourra pas utiliser les index. Dans la mesure du possible, la recherche de texte depuis le début (CD% BLUE%) est plus efficace.
surfen
@surfen patindex est pire que cela cependant, il n'utilisera pas l'index même sans% devant, la recherche de (BLUE CD%) avec patindex n'utilisera pas l'index de colonne.
BlackICE
22

Mise à jour: dans EF 6.2, il existe un opérateur similaire

Where(obj => DbFunctions.Like(obj.Column , "%expression%")
Lode Vlaeminck
la source
Ne serait - ce pas un exemple plus clair: Where(obj => DbFunctions.Like(obj.Column , "%expression%")?
DCD
Tout à fait. Changé
Lode Vlaeminck
19

Un LIKEopérateur est ajouté dans Entity Framework Core 2.0:

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

Comparé à ... where e.Title.Contains("developer") ...cela, il est vraiment traduit SQL LIKEplutôt que CHARINDEXnous ne voyons la Containsméthode.

Dmitry Pavlov
la source
5

Il est spécifiquement mentionné dans la documentation dans le cadre d'Entity SQL. Recevez-vous un message d'erreur?

// 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
la source
1
Je serais tenté de rester à l'écart d'Entity SQL au cas où vous voudriez vous éloigner d'EF à l'avenir. Soyez prudent et tenez-vous-en aux options Contains (), StartsWith () et EndsWith () dans la réponse d'origine.
Stephen Newman
1
Cela se compile bien, mais échoue à l'exécution.
brien
Le code que j'ai publié échoue à l'exécution? Cela vient du lien Microsoft.
Robert Harvey
J'ai édité la question avec un lien vers un article de blog décrivant le même problème que nous rencontrons.
brien
On dirait que Contains () est votre ticket. Mais comme l'a souligné Jon Skeet, vous devrez peut-être passer à un SQL réel manipulant directement la base de données, si Contains ne répond pas à vos besoins.
Robert Harvey
2

si vous utilisez MS Sql, j'ai écrit 2 méthodes d'extension pour prendre en charge le caractère% pour la recherche générique. (LinqKit est requis)

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

dans ef6 et cela devrait se traduire par

....
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 ='% Salut% ', @ p__linq_2 ='% Active '

Steven Chong
la source
merci pour votre commentaire Ronel, est-ce que je peux vous aider? quel est le message d'erreur?
Steven Chong
2

Pour EfCore, voici un exemple pour construire une expression 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
la source
0

Vous pouvez utiliser un réel comme dans Link to Entities assez facilement

Ajouter

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

à votre EDMX dans cette balise:

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

Souvenez-vous également de l'espace de noms dans l' <schema namespace="" />attribut

Ajoutez ensuite une classe d'extension dans l'espace de noms ci-dessus:

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

Cette méthode d'extension sera désormais mappée à la fonction EDMX.

Plus d'infos ici: http://jendaperl.blogspot.be/2011/02/like-in-linq-to-entities.html

brechtvhb
la source