LIKE оператор у LINQ


89

Чи є спосіб порівняти рядки у виразі C # LINQ, подібний до LIKEоператора SQL ?

Припустимо, у мене є список рядків. У цьому списку я хочу шукати рядок. У SQL я міг написати:

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

Замість наведеного вище, запиту потрібен синтаксис linq.

using System.Text.RegularExpressions;
…

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

Мій вищезазначений синтаксис LINQ не працює. Що я помилився?


1
Цей запит по суті спрацював для мене, коли ви його встановили. Але я використовую драйвер MongoDb Linq, і у кожного постачальника Linq є різниці у реалізації ... у будь-якому випадку, дякую.
Марк Евер

Це найкраще рішення, яке я знайшов, як у LINQ. Дякую. - @ Пранай-Рана
Абхішек Томар

Відповіді:


142

Зазвичай ви використовуєте String.StartsWith/ EndsWith/ Contains. Наприклад:

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

Я не знаю, чи є спосіб робити правильні регулярні вирази через LINQ to SQL. (Зверніть увагу, що це насправді залежить від того, якого постачальника ви використовуєте - це було б непогано в LINQ to Objects; справа в тому, чи може провайдер перетворити виклик у свій власний формат запиту, наприклад, SQL.)

EDIT: Як каже BitKFu, його Singleслід використовувати, коли ви очікуєте рівно одного результату - коли помилка полягає в тому, що це не так. Варіанти SingleOrDefault, FirstOrDefaultабо Firstповинні бути використані в залежності від точності те , що очікується.


друже, але є одна проблема, мій список містить "BALTIMORE", а мій заданий параметр порівняння - "BALTIMORE [MD], США". Вище не вдалося вибрати синтаксис.
shamim

2
погляньте на моє твердження нижче, воно може походити від методу Single (). Краще використовувати FirstOrDefault ()
BitKFu

3
@shamim: Отже, ваші дані не містять рядка, який ви шукаєте? Як би ви очікували, що це спрацює навіть у SQL?
Джон Скіт,

У SQL ви можете не отримати набору результатів - у C # ви отримаєте виняток. Що дещо інше, а не результати. Ось чому я рекомендував використовувати FirstOrDefault.
BitKFu

@BitKFu з початкової точки Single(), SingleOrDefault()буде моїм наступним кроком, якщо ми не зрозуміємо повний контекст ...
Марк Гравелл

34

Regex? немає. Але для цього запиту ви можете просто використовувати:

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

Якщо ви дійсно хочете SQL LIKE, ви можете використовувати System.Data.Linq.SqlClient.SqlMethods.Like(...), який LINQ-to-SQL відображається LIKEв SQL Server.


@Maslow - боюсь, це не моя область знань, але я не вірю, що існує хороший чистий спосіб відобразити це на всіх реалізаціях EF, тому ... ні.
Марк Гравелл

2
це може працювати на реалізаціях SQL, але не працює зі стандартною колекцією об'єктів
Кріс Макграт,

13

Ну ... іноді це може бути незручно у використанні Contains, StartsWithабо EndsWithособливо під час пошуку значення визначає LIKEстатизм, наприклад, переданий 'value%' вимагає від розробника використання StartsWithфункції у виразі. Тому я вирішив написати розширення для IQueryableоб’єктів.

Використання

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

Код

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

У вас є версія, з якою працює IEnumerable?
Nicke Manarin

8

Як вже згадували Джон Скіт та Марк Гравелл, ви можете просто взяти умову містить. Але у випадку вашого подібного запиту дуже небезпечно взяти оператор Single (), оскільки це означає, що ви знайдете лише 1 результат. У разі отримання більших результатів ви отримаєте хороший виняток :)

Тому я б віддав перевагу використанню FirstOrDefault () замість Single ():

var first = Database.DischargePorts.FirstOrDefault(p => p.PortName.Contains("BALTIMORE"));
var portcode = first != null ? first.PortCode : string.Empty;

якщо ми запевняємо, що існує рівно один збіг, Single не є "небезпечним" - це "правильним". Все зводиться до того, що ми вимагаємо щодо даних ... "будь-яке число", "принаймні одне", "щонайбільше одне", "рівно одне" тощо
Марк Гравелл

3
залежно від контексту, це може бути ... це повністю залежить від очікування запиту
Марк Гравелл

А як щодо "порожнього" або "%" пошуку? Чи міг би це обробляти "B", "BALT" та "" (маючи на увазі все мені)?
BlueChippy

8

У рідному LINQ ви можете використовувати комбінацію Contains/StartsWith/EndsWithабо RegExp.

У LINQ2SQL використовується метод SqlMethods.Like()

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

додайте Assembly: System.Data.Linq (у System.Data.Linq.dll), щоб використовувати цю функцію.


Я розумію, що в OP насправді не сказано Linq2SQL, але, здавалося, це мається на увазі. Причина, по якій я тут, полягає в тому StartsWith(), що Contains(), тощо, не працюють з Linq2SQL (принаймні я отримую "Вираз LINQ ... не можна перекласти ..." та інструкцію щодо використання ToList () для "оцінки клієнта" - що я я це вже роблю. Зверніть увагу, в EF Core він перенесений наEF.Functions.Like()
Auspex

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

Це працює як "ПОДОБАЄТЬСЯ" SQL ...


8
ні .. ні це не це працює лише як LIKE 'term%', який далеко не працює як подібний оператор в цілому і не підтримує символи підстановки
Chris McGrath

3

Простий, як цей

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

Результат -> Аннік, Яннік


2

Ви можете викликати єдиний метод із предикатом:

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

2

В ідеалі ви повинні використовувати StartWithабоEndWith .

Ось приклад:

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

Просто додайте до рядка методи розширення об'єкта.

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

використання:

use namespase that contains this class;

var sPortCode = Database.DischargePorts
            .Where(p => p.PortName.Contains(new string [] {"BALTIMORE"},  StringComparison.CurrentCultureIgnoreCase) )
            .Single().PortCode;


0

@adobrzyc мав цю чудову спеціальну LIKEфункцію - я просто хотів поділитися її IEnumerableверсією.

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;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.