Opérateur LIKE dans LINQ

88

Existe-t-il un moyen de comparer des chaînes dans une expression C # LINQ similaire à l' LIKEopérateur SQL ?

Supposons que j'ai une liste de chaînes. Sur cette liste, je veux rechercher une chaîne. En SQL, je pourrais écrire:

SELECT * FROM DischargePort WHERE PortName LIKE '%BALTIMORE%'

Au lieu de ce qui précède, la requête veut une syntaxe linq.

using System.Text.RegularExpressions;


var regex = new Regex(sDischargePort, RegexOptions.IgnoreCase);
var sPortCode = Database.DischargePorts
                .Where(p => regex.IsMatch(p.PortName))
                .Single().PortCode;

Ma syntaxe LINQ ci-dessus ne fonctionne pas. Qu'est-ce que je me trompe?

shamim
la source
1
Cette requête a essentiellement fonctionné pour moi lorsque vous l'avez mise en place. Mais j'utilise le pilote MongoDb Linq et il existe des différences d'implémentation dans chaque fournisseur Linq ... de toute façon, merci.
Mark Ewer
C'est la meilleure solution que j'ai trouvée pour comme dans LINQ. Merci. - @ Pranay-Rana
Abhishek Tomar

Réponses:

140

Généralement, vous utilisez String.StartsWith/ EndsWith/ Contains. Par exemple:

var portCode = Database.DischargePorts
                       .Where(p => p.PortName.Contains("BALTIMORE"))
                       .Single()
                       .PortCode;

Je ne sais pas s'il existe un moyen de créer des expressions régulières appropriées via LINQ to SQL. (Notez que cela dépend vraiment du fournisseur que vous utilisez - ce serait bien dans LINQ to Objects; il s'agit de savoir si le fournisseur peut convertir l'appel dans son format de requête natif, par exemple SQL.)

EDIT: Comme le dit BitKFu, Singledoit être utilisé lorsque vous attendez exactement un résultat - quand c'est une erreur pour que ce ne soit pas le cas. Les options de SingleOrDefault, FirstOrDefaultou Firstdoivent être utilisées en fonction exactement de ce qui est attendu.

Jon Skeet
la source
ami mais, il y a un problème, ma liste contient "BALTIMORE", et mon paramètre de comparaison donné est "BALTIMORE [MD], US". La syntaxe ci-dessus ne parvient pas à sélectionner.
shamim
2
jetez un œil à ma déclaration ci-dessous, cela pourrait provenir de la méthode Single (). Il est préférable d'utiliser FirstOrDefault ()
BitKFu
3
@shamim: Vos données ne contiennent donc pas la chaîne que vous recherchez? Comment vous attendez-vous à ce que cela fonctionne même en SQL?
Jon Skeet
En SQL, vous n'obtiendrez peut-être aucun jeu de résultats - en C #, vous recevrez une exception. Ce qui est légèrement différent, au lieu d'aucun résultat. C'est pourquoi j'ai recommandé d'utiliser FirstOrDefault.
BitKFu
@BitKFu à partir d'un point de départ de Single(), SingleOrDefault()serait ma prochaine étape, à moins que nous ne comprenions le contexte complet ...
Marc Gravell
34

Regex? non. Mais pour cette requête, vous pouvez simplement utiliser:

 string filter = "BALTIMORE";
 (blah) .Where(row => row.PortName.Contains(filter)) (blah)

Si vous voulez vraiment SQL LIKE, vous pouvez utiliser System.Data.Linq.SqlClient.SqlMethods.Like(...), auquel LINQ-to-SQL mappe LIKEdans SQL Server.

Marc Gravell
la source
@Maslow - ce n'est pas mon domaine d'expertise, j'en ai bien peur - mais je ne crois pas qu'il existe une bonne manière propre de cartographier cela avec toutes les implémentations EF, donc ... non.
Marc Gravell
2
cela peut fonctionner sur les implémentations SQL mais ne fonctionne pas avec une collection d'objets standard
Chris McGrath
13

Eh bien ... parfois, il peut être inconfortable à utiliser Contains, StartsWithou en EndsWithparticulier lors de la recherche de valeur, déterminer la LIKEdéclaration, par exemple, "valeur%" passé, nécessite du développeur d'utiliser la StartsWithfonction dans l'expression. J'ai donc décidé d'écrire une extension pour les IQueryableobjets.

Usage

// numbers: 11-000-00, 00-111-00, 00-000-11

var data1 = parts.Like(p => p.Number, "%11%");
// result: 11-000-00, 00-111-00, 00-000-11

var data2 = parts.Like(p => p.Number, "11%");
// result: 11-000-00

var data3 = parts.Like(p => p.Number, "%11");
// result: 00-000-11

Code

public static class LinqEx
{
    private static readonly MethodInfo ContainsMethod = typeof(string).GetMethod("Contains");
    private static readonly MethodInfo StartsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
    private static readonly MethodInfo EndsWithMethod = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });

    public static Expression<Func<TSource, bool>> LikeExpression<TSource, TMember>(Expression<Func<TSource, TMember>> property, string value)
    {
        var param = Expression.Parameter(typeof(TSource), "t");
        var propertyInfo = GetPropertyInfo(property);
        var member = Expression.Property(param, propertyInfo.Name);

        var startWith = value.StartsWith("%");
        var endsWith = value.EndsWith("%");

        if (startWith)
            value = value.Remove(0, 1);

        if (endsWith)
            value = value.Remove(value.Length - 1, 1);

        var constant = Expression.Constant(value);
        Expression exp;

        if (endsWith && startWith)
        {
            exp = Expression.Call(member, ContainsMethod, constant);
        }
        else if (startWith) 
        {
            exp = Expression.Call(member, EndsWithMethod, constant);
        }
        else if (endsWith)
        {
            exp = Expression.Call(member, StartsWithMethod, constant);
        }
        else
        {
            exp = Expression.Equal(member, constant);
        }

        return Expression.Lambda<Func<TSource, bool>>(exp, param);
    }

    public static IQueryable<TSource> Like<TSource, TMember>(this IQueryable<TSource> source, Expression<Func<TSource, TMember>> parameter, string value)
    {
        return source.Where(LikeExpression(parameter, value));
    }

    private static PropertyInfo GetPropertyInfo(Expression expression)
    {
        var lambda = expression as LambdaExpression;
        if (lambda == null)
            throw new ArgumentNullException("expression");

        MemberExpression memberExpr = null;

        switch (lambda.Body.NodeType)
        {
            case ExpressionType.Convert:
                memberExpr = ((UnaryExpression)lambda.Body).Operand as MemberExpression;
                break;
            case ExpressionType.MemberAccess:
                memberExpr = lambda.Body as MemberExpression;
                break;
        }

        if (memberExpr == null)
            throw new InvalidOperationException("Specified expression is invalid. Unable to determine property info from expression.");


        var output = memberExpr.Member as PropertyInfo;

        if (output == null)
            throw new InvalidOperationException("Specified expression is invalid. Unable to determine property info from expression.");

        return output;
    }
}
adobrzyc
la source
Avez-vous une version qui fonctionne avec IEnumerable?
Nicke Manarin
8

Comme Jon Skeet et Marc Gravell l'ont déjà mentionné, vous pouvez simplement prendre une condition contient. Mais dans le cas de votre requête similaire, il est très dangereux de prendre une instruction Single (), car cela implique que vous ne trouvez qu'un seul résultat. En cas de plus de résultats, vous recevrez une belle exception :)

Je préférerais donc utiliser FirstOrDefault () au lieu de Single ():

var first = Database.DischargePorts.FirstOrDefault(p => p.PortName.Contains("BALTIMORE"));
var portcode = first != null ? first.PortCode : string.Empty;
BitKFu
la source
si nous nous attendons à ce qu'il y ait exactement un match, Single n'est pas "dangereux" - c'est "correct". Tout se résume à ce que nous prétendons sur les données ... "n'importe quel nombre", "au moins un", "au plus un", "exactement un", etc.
Marc Gravell
3
selon le contexte, cela peut être ... cela dépend entièrement de l'attente de la requête
Marc Gravell
Qu'en est-il d'une recherche «vide» ou «%»? Est-ce que cela pourrait gérer "B", "BALT" et "" (ce qui veut dire me donner tout)?
BlueChippy
8

Dans LINQ natif, vous pouvez utiliser une combinaison de Contains/StartsWith/EndsWithou RegExp.

Dans LINQ2SQL, utilisez la méthode SqlMethods.Like()

    from i in db.myTable
    where SqlMethods.Like(i.field, "tra%ata")
    select i

ajoutez Assembly: System.Data.Linq (dans System.Data.Linq.dll) pour utiliser cette fonctionnalité.

Marat Batalandabad
la source
Je comprends que l'OP n'a pas réellement dit Linq2SQL, mais cela semblait implicite. La raison pour laquelle je suis ici est que StartsWith(), Contains()etc., ne fonctionne pas avec Linq2SQL (au moins j'obtiens "L'expression LINQ ... n'a pas pu être traduite ..." et une instruction pour utiliser ToList () pour "l'évaluation du client" - ce que je ' Je le fais déjà. Notez que dans EF Core, il est déplacé versEF.Functions.Like()
Auspex le
3
  .Where(e => e.Value.StartsWith("BALTIMORE"))

Cela fonctionne comme "LIKE" de SQL ...

user1930698
la source
8
non .. non ça ne fonctionne pas seulement comme LIKE 'term%' qui est loin de fonctionner comme l'opérateur like dans son ensemble et ne prend pas en charge les jokers
Chris McGrath
3

Un simple comme ça

string[] users = new string[] {"Paul","Steve","Annick","Yannick"};    
var result = from u in users where u.Contains("nn") select u;

Résultat -> Annick, Yannick

Yannick Turbang
la source
2

Vous pouvez appeler la méthode unique avec un prédicat:

var portCode = Database.DischargePorts
                   .Single(p => p.PortName.Contains("BALTIMORE"))
                   .PortCode;
Zebi
la source
2

Idéalement, vous devriez utiliser StartWithou EndWith.

Voici un exemple:

DataContext  dc = new DCGeneral();
List<Person> lstPerson= dc.GetTable<Person>().StartWith(c=> c.strNombre).ToList();

return lstPerson;
Eduardo Romero Marin
la source
0
   public static class StringEx
    {
        public static bool Contains(this String str, string[] Arr, StringComparison comp)
        {
            if (Arr != null)
            {
                foreach (string s in Arr)
                {
                    if (str.IndexOf(s, comp)>=0)
                    { return true; }
                }
            }

            return false;
        }

        public static bool Contains(this String str,string[] Arr)
        {
            if (Arr != null)
            {
                foreach (string s in Arr)
                {
                    if (str.Contains(s))
                    { return true; }
                }
            }

            return false;
        }
    }


var portCode = Database.DischargePorts
                   .Single(p => p.PortName.Contains( new string[] {"BALTIMORE"},  StringComparison.CurrentCultureIgnoreCase) ))
                   .PortCode;
NoBrend s
la source
0

Ajoutez simplement aux méthodes d'extension d'objet de chaîne.

public static class StringEx
{
    public static bool Contains(this String str, string[] Arr, StringComparison comp)
    {
        if (Arr != null)
        {
            foreach (string s in Arr)
            {
                if (str.IndexOf(s, comp)>=0)
                { return true; }
            }
        }

        return false;
    }

    public static bool Contains(this String str,string[] Arr)
    {
        if (Arr != null)
        {
            foreach (string s in Arr)
            {
                if (str.Contains(s))
                { return true; }
            }
        }

        return false;
    }
}

usage:

use namespase that contains this class;

var sPortCode = Database.DischargePorts
            .Where(p => p.PortName.Contains(new string [] {"BALTIMORE"},  StringComparison.CurrentCultureIgnoreCase) )
            .Single().PortCode;
NoBrend s
la source
0
List<Categories> categoriess;
        private void Buscar()
        {
            try
            {
                categoriess = Contexto.Categories.ToList();
                categoriess = categoriess.Where(n => n.CategoryID >= Convert.ToInt32(txtCatID.Text) && n.CategoryID <= Convert.ToInt32(txtCatID1.Text) && (n.CategoryName.Contains(txtCatName.Text)) ).ToList();
Eber Camacho
la source
pensez à jeter un oeil à comment rédiger une bonne réponse
Aissani Abdelillah
0

@adobrzyc avait cette excellente LIKEfonction personnalisée - je voulais juste en partager la IEnumerableversion.

public static class LinqEx
{
    private static readonly MethodInfo ContainsMethod = typeof(string).GetMethod("Contains");
    private static readonly MethodInfo StartsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
    private static readonly MethodInfo EndsWithMethod = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });

    private static Func<TSource, bool> LikeExpression<TSource, TMember>(Expression<Func<TSource, TMember>> property, string value)
    {
        var param = Expression.Parameter(typeof(TSource), "t");
        var propertyInfo = GetPropertyInfo(property);
        var member = Expression.Property(param, propertyInfo.Name);

        var startWith = value.StartsWith("%");
        var endsWith = value.EndsWith("%");

        if (startWith)
            value = value.Remove(0, 1);

        if (endsWith)
            value = value.Remove(value.Length - 1, 1);

        var constant = Expression.Constant(value);
        Expression exp;

        if (endsWith && startWith)
        {
            exp = Expression.Call(member, ContainsMethod, constant);
        }
        else if (startWith)
        {
            exp = Expression.Call(member, EndsWithMethod, constant);
        }
        else if (endsWith)
        {
            exp = Expression.Call(member, StartsWithMethod, constant);
        }
        else
        {
            exp = Expression.Equal(member, constant);
        }

        return Expression.Lambda<Func<TSource, bool>>(exp, param).Compile();
    }

    public static IEnumerable<TSource> Like<TSource, TMember>(this IEnumerable<TSource> source, Expression<Func<TSource, TMember>> parameter, string value)
    {
        return source.Where(LikeExpression(parameter, value));
    }


    private static PropertyInfo GetPropertyInfo(Expression expression)
    {
        var lambda = expression as LambdaExpression;
        if (lambda == null)
            throw new ArgumentNullException("expression");

        MemberExpression memberExpr = null;

        switch (lambda.Body.NodeType)
        {
            case ExpressionType.Convert:
                memberExpr = ((UnaryExpression)lambda.Body).Operand as MemberExpression;
                break;
            case ExpressionType.MemberAccess:
                memberExpr = lambda.Body as MemberExpression;
                break;
        }

        if (memberExpr == null)
            throw new InvalidOperationException("Specified expression is invalid. Unable to determine property info from expression.");


        var output = memberExpr.Member as PropertyInfo;

        if (output == null)
            throw new InvalidOperationException("Specified expression is invalid. Unable to determine property info from expression.");

        return output;
    }
}
Steve
la source