Pytanie LIKE operator w LINQ


Czy istnieje sposób na porównanie ciągów w wyrażeniu C # LINQ podobne do SQL LIKE operator?

Załóżmy, że mam listę ciągów. Na tej liście chcę wyszukać ciąg znaków. W SQL mogę 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 mam źle?


76
2018-03-21 06:26


pochodzenie


Ta kwerenda zasadniczo zadziałała dla mnie, gdy ją wstawiłeś. Ale używam sterownika MongoDb Linq i istnieją różnice w implementacji w każdym dostawcy Linq ... tak, dzięki. - Mark Ewer


Odpowiedzi:


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

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

Nie wiem, czy istnieje sposób na poprawne wyrażeń regularnych za pośrednictwem LINQ do SQL. (Zauważ, że tak naprawdę zależy to od dostawcy, z którego korzystasz - w LINQ do Objects byłoby dobrze, zależy to od tego, czy dostawca może przekonwertować wywołanie na format natywnego zapytania, np. SQL).

EDYCJA: Jak mówi BitKFu, Single powinny być używane, gdy tego oczekujesz dokładnie jeden wynik - gdy jest to błędem, aby tak nie było. Opcje SingleOrDefault, FirstOrDefault lub First powinien być stosowany w zależności od dokładnie czego się spodziewać.


120
2018-03-21 06:28



ale istnieje jeden problem, moja lista zawiera "BALTIMORE", a moim danym parametrem porównania jest "BALTIMORE [MD], US". Powyższa składnia nie daje wyboru. - shamim
spójrz na moje zdanie poniżej, może ono pochodzić z metody Single (). Lepiej użyć FirstOrDefault () - BitKFu
@shamim: Twoje dane nie zawierają szukanego ciągu? Jak można oczekiwać, że będzie działać nawet w SQL? - Jon Skeet
W SQL możesz nie uzyskać żadnego zestawu wyników - w języku C # otrzymasz wyjątek. Co jest nieco inne, zamiast braku wyników. Dlatego zaleciłem użycie FirstOrDefault. - BitKFu
@BitKFu od punktu początkowego Single(), SingleOrDefault()byłby to mój następny krok, chyba że zrozumiemy pełny kontekst ... - Marc Gravell♦


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

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

Jeśli ty naprawdę chcesz SQL LIKE, możesz użyć System.Data.Linq.SqlClient.SqlMethods.Like(...), do których map LINQ-SQL LIKE w SQL Server.


28
2018-03-21 06:29



a co powiesz na EF4? - Maslow
@Maslow - nie moja dziedzina wiedzy, obawiam się - ale nie wierzę, że istnieje ładny, czysty sposób mapowania tego do wszystkich wdrożeń EF, więc ... nie. - Marc Gravell♦
może to działać na implementacjach SQL, ale nie działa ze standardowym gromadzeniem obiektów - Chris McGrath


Jak wspomnieli już Jon Skeet i Marc Gravell, możesz łatwo wziąć warunek zawierający. Ale w przypadku podobnego zapytania bardzo niebezpieczne jest wykonanie instrukcji Single (), ponieważ oznacza to, że znajduje się tylko jeden wynik. W przypadku większej liczby 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;

8
2018-03-21 06:34



jeśli to jest nasze zapewnił oczekiwanie że jest dokładnie jeden mecz, singiel nie jest "niebezpieczny" - jest "poprawny". Wszystko sprowadza się do tego, co mówimy o danych ... "dowolna liczba", "co najmniej jedna", "najwyżej jedna", "dokładnie jedna", itd. - Marc Gravell♦
tak, ale LIKE Query prawdopodobnie nie jest pojedynczym wynikiem;) - BitKFu
w zależności od kontekstu może być ... zależy całkowicie od oczekiwań zapytania - Marc Gravell♦
A co z wyszukiwaniem "pustym" lub "%"? Czy to może oznaczać "B", "BALT" i "" (co oznacza, że ​​dostajesz wszystko)? - BlueChippy


Cóż ... czasami może być niewygodne w użyciu Contains, StartsWith lub EndsWith zwłaszcza przy wyszukiwaniu wartości LIKE ocena np. przekazano "wartość%" wymaganą od programisty do użycia StartsWith funkcja w wyrażeniu. Więc postanowiłem napisać rozszerzenie dla IQueryable obiekty.

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

7
2018-02-25 19:07



Czy masz wersję, która współpracuje z IEnumerable? - Nicke Manarin


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

W metodzie używania LINQ2SQL SqlMethods.Like()

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

dodać Assembly: System.Data.Linq (w System.Data.Linq.dll), aby korzystać z tej funkcji.


6
2018-04-06 15:10





To proste

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

Wynik -> Annick, Yannick


3
2018-01-01 19:14





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

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

2
2018-03-21 06:32





  .Where(e => e.Value.StartsWith("BALTIMORE"))

Działa to jak "LIKE" SQL ...


2
2017-12-26 20:58



nie, nie, nie działa tylko tak jak LIKE 'term%', który jest daleki od działania jak operator podobny jako całość i nie obsługuje znaków wieloznacznych - Chris McGrath