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?


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


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.


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

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;

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.


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


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


2

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

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

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;

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;

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;

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


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;
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.