Operator LIKE w LINQ

89

Czy istnieje sposób, aby porównać ciągi w wyrażeniu C # LINQ podobnym do LIKEoperatora SQL ?

Załóżmy, że mam listę ciągów. Na tej liście chcę wyszukać ciąg. W SQL mógłbym napisać:

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

Zamiast powyższego, zapytanie wymaga składni linq.

using System.Text.RegularExpressions;
…

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

Moja powyższa składnia LINQ nie działa. Co się stało?

shamim
źródło
1
To zapytanie zasadniczo działało dla mnie, gdy je umieściłeś. Ale używam sterownika MongoDb Linq i istnieją różnice w implementacji u każdego dostawcy Linq ... w każdym razie, dzięki.
Mark Ewer
To najlepsze rozwiązanie, jakie znalazłem w LINQ. Dzięki. - @ Pranay-Rana
Abhishek Tomar

Odpowiedzi:

143

Zwykle używasz String.StartsWith/ EndsWith/ Contains. Na przykład:

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

Nie wiem jednak, czy istnieje sposób na wykonanie odpowiednich wyrażeń regularnych za pośrednictwem LINQ to SQL. (Zauważ, że tak naprawdę zależy to od dostawcy, którego używasz - byłoby dobrze w LINQ to Objects; jest to kwestia tego, czy dostawca może przekonwertować wywołanie na jego natywny format zapytania, np. SQL).

EDYCJA: Jak mówi BitKFu, Singlepowinno być używane, gdy oczekujesz dokładnie jednego wyniku - jeśli jest to błąd, aby tak nie było. Opcje SingleOrDefault, FirstOrDefaultczy Firstnależy stosować w zależności od dokładnie , co się spodziewać.

Jon Skeet
źródło
przyjacielu, ale jest jeden problem, moja lista zawiera „BALTIMORE”, a podany przeze mnie parametr porównania to „BALTIMORE [MD], US”. Nie można wybrać powyższej składni.
shamim
2
spójrz na moje oświadczenie poniżej, może ono pochodzić z metody Single (). Lepiej jest użyć FirstOrDefault ()
BitKFu
3
@shamim: Więc twoje dane nie zawierają ciągu, którego szukasz? Jak można się spodziewać, że to zadziała nawet w SQL?
Jon Skeet,
W SQL możesz nie otrzymać zestawu wyników - w C # otrzymasz wyjątek. Co jest nieco inne, zamiast żadnych wyników. Dlatego zaleciłem użycie FirstOrDefault.
BitKFu
@BitKFu od punktu wyjścia Single(), SingleOrDefault()byłby moim następnym krokiem, chyba że rozumiemy pełny kontekst ...
Marc Gravell
34

Regex? Nie. Ale do tego zapytania możesz po prostu użyć:

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

Jeśli naprawdę chcesz SQL LIKE, możesz użyć System.Data.Linq.SqlClient.SqlMethods.Like(...)mapowania LINQ-to-SQL LIKEw SQL Server.

Marc Gravell
źródło
@Maslow - obawiam się, że to nie moja specjalizacja - ale nie wierzę, że istnieje ładny, czysty sposób odwzorowania tego na wszystkie implementacje EF, więc ... nie.
Marc Gravell
2
może to działać w przypadku implementacji SQL, ale nie działa ze standardową kolekcją obiektów
Chris McGrath,
13

Cóż ... czasami może być niewygodne w użyciu Contains, StartsWitha EndsWithzwłaszcza gdy wyszukiwanie wartości określa LIKEinstrukcję, np. Przekazana „wartość%” wymaga od programisty użycia StartsWithfunkcji w wyrażeniu. Postanowiłem więc napisać rozszerzenie dla IQueryableobiektów.

Stosowanie

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

Kod

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
źródło
Czy masz wersję, z którą współpracuje IEnumerable?
Nicke Manarin
8

Jak już wspomnieli Jon Skeet i Marc Gravell, możesz po prostu wziąć warunek zawierający. Ale w przypadku podobnego zapytania bardzo niebezpieczne jest wykonanie instrukcji Single (), ponieważ oznacza to, że znajdziesz tylko 1 wynik. W przypadku większej ilości wyników otrzymasz miły wyjątek :)

Więc wolałbym używać FirstOrDefault () zamiast Single ():

var first = Database.DischargePorts.FirstOrDefault(p => p.PortName.Contains("BALTIMORE"));
var portcode = first != null ? first.PortCode : string.Empty;
BitKFu
źródło
jeśli jest naszym zapewnionym oczekiwaniem , że jest dokładnie jedno dopasowanie, Single nie jest „niebezpieczne” - jest „poprawne”. Wszystko sprowadza się do tego, co twierdzimy o danych… „dowolna liczba”, „co najmniej jeden”, „co najwyżej jeden”, „dokładnie jeden” itd.
Marc Gravell
3
w zależności od kontekstu może to być ... zależy to całkowicie od oczekiwanego zapytania
Marc Gravell
A co z wyszukiwaniem „puste” lub „%”? Czy to może obsłużyć „B”, „BALT” i „” (co oznacza, że ​​dostanę wszystko)?
BlueChippy,
8

W natywnym LINQ możesz użyć kombinacji Contains/StartsWith/EndsWithlub RegExp.

W LINQ2SQL użyj metody SqlMethods.Like()

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

dodaj Assembly: System.Data.Linq (w System.Data.Linq.dll), aby użyć tej funkcji.

Marat Batalandabad
źródło
Rozumiem, że OP tak naprawdę nie powiedział Linq2SQL, ale wydawało się to sugerowane. Powodem jestem tutaj jest to, że StartsWith(), Contains()itd, czy nie praca z linq2sql (przynajmniej mam „Wyrażenie LINQ ... nie można przetłumaczyć ...” i nakłanianie do użycia ToList () dla „klient” ewaluacji-co ja” m już robię. Uwaga, w EF Core jest przeniesiony doEF.Functions.Like()
Auspex
3
  .Where(e => e.Value.StartsWith("BALTIMORE"))

To działa jak „LIKE” w SQL ...

user1930698
źródło
8
nie ... nie, nie działa tylko jak LIKE 'term%', który jest daleki od działania jak podobny operator jako całość i nie obsługuje symboli wieloznacznych
Chris McGrath
3

Takie proste

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

Wynik -> Annick, Yannick

Yannick Turbang
źródło
2

Możesz wywołać pojedynczą metodę z predykatem:

var portCode = Database.DischargePorts
                   .Single(p => p.PortName.Contains("BALTIMORE"))
                   .PortCode;
Zebi
źródło
2

Najlepiej byłoby użyć StartWithlub EndWith.

Oto przykład:

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

return lstPerson;
Eduardo Romero Marin
źródło
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
źródło
0

Po prostu dodaj metody rozszerzania obiektów typu string.

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

stosowanie:

use namespase that contains this class;

var sPortCode = Database.DischargePorts
            .Where(p => p.PortName.Contains(new string [] {"BALTIMORE"},  StringComparison.CurrentCultureIgnoreCase) )
            .Single().PortCode;
NoBrend s
źródło
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
źródło
zastanów się, jak napisać dobrą odpowiedź
Aissani Abdelillah
0

@adobrzyc miał tę świetną niestandardową LIKEfunkcję - chciałem tylko udostępnić jej IEnumerablewersję.

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