LIKE-Operator in LINQ

89

Gibt es eine Möglichkeit, Zeichenfolgen in einem C # LINQ-Ausdruck zu vergleichen, der dem SQL- LIKEOperator ähnelt ?

Angenommen, ich habe eine Zeichenfolgenliste. Auf dieser Liste möchte ich eine Zeichenfolge suchen. In SQL könnte ich schreiben:

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

Anstelle der oben genannten Abfrage möchte eine Linq-Syntax.

using System.Text.RegularExpressions;


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

Meine obige LINQ-Syntax funktioniert nicht. Was habe ich falsch gemacht?

Shamim
quelle
1
Diese Abfrage hat im Wesentlichen für mich funktioniert, als Sie sie eingerichtet haben. Aber ich verwende den MongoDb Linq-Treiber und es gibt Implementierungsunterschiede bei jedem Linq-Anbieter ... trotzdem, danke.
Mark Ewer
Dies ist die beste Lösung, die ich wie in LINQ gefunden habe. Vielen Dank. - @ Pranay-Rana
Abhishek Tomar

Antworten:

140

Normalerweise verwenden Sie String.StartsWith/ EndsWith/ Contains. Beispielsweise:

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

Ich weiß nicht, ob es eine Möglichkeit gibt, korrekte reguläre Ausdrücke über LINQ to SQL zu erstellen. (Beachten Sie, dass es wirklich davon abhängt, welchen Anbieter Sie verwenden. In LINQ to Objects ist dies in Ordnung. Es kommt darauf an, ob der Anbieter den Aufruf in sein natives Abfrageformat konvertieren kann, z. B. SQL.)

BEARBEITEN: Wie BitKFu sagt, Singlesollte verwendet werden, wenn Sie genau ein Ergebnis erwarten - wenn es ein Fehler ist, dass dies nicht der Fall ist. Optionen SingleOrDefault, FirstOrDefaultoder Firstsollten je nach verwendet werden , genau , was erwartet wird .

Jon Skeet
quelle
Freund, aber es gibt ein Problem: Meine Liste enthält "BALTIMORE" und mein angegebener Vergleichsparameter ist "BALTIMORE [MD], US". Die obige Syntax kann nicht ausgewählt werden.
Shamim
2
Schauen Sie sich meine Aussage unten an, sie könnte von der Single () -Methode stammen. Es ist besser, FirstOrDefault ()
BitKFu
3
@shamim: Ihre Daten enthalten also nicht die gesuchte Zeichenfolge? Wie würden Sie erwarten, dass dies auch in SQL funktioniert?
Jon Skeet
In SQL erhalten Sie möglicherweise keine Ergebnismenge - in C # erhalten Sie eine Ausnahme. Welches ist etwas anders, anstatt keine Ergebnisse. Deshalb habe ich empfohlen, FirstOrDefault zu verwenden.
BitKFu
@BitKFu von einem Ausgangspunkt von Single(), SingleOrDefault()wäre mein nächster Schritt, es sei denn, wir verstehen den vollständigen Kontext ...
Marc Gravell
34

Regex? Nein. Aber für diese Abfrage können Sie einfach verwenden:

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

Wenn Sie wirklich SQL möchten LIKE, können Sie das verwenden System.Data.Linq.SqlClient.SqlMethods.Like(...), dem LINQ-to-SQL LIKEin SQL Server zugeordnet ist.

Marc Gravell
quelle
@Maslow - ich fürchte, nicht mein Fachgebiet - aber ich glaube nicht, dass es eine schöne, saubere Möglichkeit gibt, dies allen EF-Implementierungen zuzuordnen, also ... nein.
Marc Gravell
2
Dies funktioniert möglicherweise bei SQL-Implementierungen, funktioniert jedoch nicht mit einer Standardobjektsammlung
Chris McGrath,
12

Nun ... manchmal kann es unangenehm sein, die Funktion zu verwenden Contains, StartsWithoder EndsWithinsbesondere, wenn der LIKESuchwert die Aussage bestimmt, z. B. die vom Entwickler übergebene 'Wert%', um die StartsWithFunktion im Ausdruck zu verwenden. Also habe ich beschlossen, eine Erweiterung für IQueryableObjekte zu schreiben .

Verwendung

// 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
quelle
Haben Sie eine Version, die funktioniert IEnumerable?
Nicke Manarin
8

Wie Jon Skeet und Marc Gravell bereits erwähnt haben, können Sie einfach eine Contain-Bedingung annehmen. Bei einer ähnlichen Abfrage ist es jedoch sehr gefährlich, eine Single () - Anweisung zu verwenden, da dies bedeutet, dass Sie nur 1 Ergebnis finden. Bei weiteren Ergebnissen erhalten Sie eine nette Ausnahme :)

Daher würde ich lieber FirstOrDefault () anstelle von Single () verwenden:

var first = Database.DischargePorts.FirstOrDefault(p => p.PortName.Contains("BALTIMORE"));
var portcode = first != null ? first.PortCode : string.Empty;
BitKFu
quelle
Wenn wir davon ausgehen , dass es genau eine Übereinstimmung gibt, ist Single nicht "gefährlich" - es ist "richtig". Es kommt alles darauf an, was wir über die Daten behaupten ... "jede Zahl", "mindestens eine", "höchstens eine", "genau eine" usw.
Marc Gravell
3
Je nach Kontext kann es sein ... es hängt ganz von der Erwartung der Abfrage ab
Marc Gravell
Was ist mit einer "leeren" oder "%" Suche? Könnte dies mit "B", "BALT" und "" umgehen (was bedeutet, dass ich alles bekomme)?
BlueChippy
8

In nativem LINQ können Sie eine Kombination aus Contains/StartsWith/EndsWithoder RegExp verwenden.

Verwenden Sie in LINQ2SQL die Methode SqlMethods.Like()

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

Fügen Sie Assembly: System.Data.Linq (in System.Data.Linq.dll) hinzu, um diese Funktion zu verwenden.

Marat Batalandabad
quelle
Ich verstehe, dass das OP eigentlich nicht Linq2SQL sagte, aber es schien impliziert. Der Grund , warum ich hier bin , ist , dass StartsWith(), Contains()usw. Sie nicht die Arbeit mit Linq2SQL (zumindest ich „The LINQ Ausdruck ... kann nicht übersetzt werden ...“ und eine Anweisung zum Gebrauch ToList () für „Client - Bewertung“ -Welche I‘ Ich mache es bereits. Beachten Sie, dass es in EF Core aufEF.Functions.Like()
Auspex
3
  .Where(e => e.Value.StartsWith("BALTIMORE"))

Dies funktioniert wie "LIKE" von SQL ...

user1930698
quelle
8
nein .. nein es funktioniert nicht nur wie LIKE 'term%', was weit davon entfernt ist, wie der gleiche Operator als Ganzes zu funktionieren und keine Platzhalter unterstützt
Chris McGrath
3

So einfach ist das

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

Ergebnis -> Annick, Yannick

Yannick Turbang
quelle
2

Sie können die einzelne Methode mit einem Prädikat aufrufen:

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

Idealerweise sollten Sie StartWithoder verwenden EndWith.

Hier ist ein Beispiel:

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

return lstPerson;
Eduardo Romero Marin
quelle
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
quelle
0

Fügen Sie einfach Methoden zur Erweiterung von Zeichenfolgenobjekten hinzu.

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

Verwendung:

use namespase that contains this class;

var sPortCode = Database.DischargePorts
            .Where(p => p.PortName.Contains(new string [] {"BALTIMORE"},  StringComparison.CurrentCultureIgnoreCase) )
            .Single().PortCode;
NoBrend s
quelle
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
quelle
Sehen Sie sich einen Blick auf , wie man eine gute asnwer zu schreiben
Aissani Abdelillah