Оператор LIKE в LINQ


89

Есть ли способ сравнить строки в выражении LINQ C #, аналогичном оператору SQL LIKE?

Предположим, у меня есть список строк. В этом списке я хочу найти строку. В 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. Спасибо. - @ Pranay-Rana
Абхишек Томар

Не совсем понятно, что ты хочешь, а что «не получается». Вам нужен эквивалент LINQ-to-objects Likeили функция, которая преобразуется Likeв ORM, который вы используете? Если последнее, то какой ORM? Также примите один из ответов, если он вам помог. Люди продолжают накапливать ответы, основанные только на смутных предположениях о том, о чем вы спрашиваете.
Герт Арнольд

Ответы:


143

Обычно вы используете String.StartsWith/ EndsWith/ Contains. Например:

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

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

РЕДАКТИРОВАТЬ: Как говорит BitKFu, Singleследует использовать, когда вы ожидаете ровно один результат - когда это ошибка, чтобы этого не было. Варианты SingleOrDefault, FirstOrDefaultили Firstдолжны быть использованы в зависимости от точности то , что ожидается.


друг, но есть одна проблема. Мой список содержит "BALTIMORE", а данный параметр сравнения - "BALTIMORE [MD], US". Выше синтаксис не выбран.
шамим

2
взгляните на мое утверждение ниже, оно может быть получено из метода Single (). Лучше использовать FirstOrDefault ()
BitKFu

3
@shamim: Значит, ваши данные не содержат искомую строку? Как вы ожидаете, что это будет работать даже в SQL?
Джон Скит

В SQL вы можете не получить результирующий набор - в C # вы получите исключение. Что немного по-другому, а не без результатов. Вот почему я рекомендовал использовать FirstOrDefault.
BitKFu

@BitKFu, с самого начала Single(), SingleOrDefault()был бы моим следующим шагом, если мы не поймем полный контекст ...
Марк Грейвелл

34

Регулярное выражение? нет. Но для этого запроса вы можете просто использовать:

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

Если вам действительно нужен SQL LIKE, вы можете использовать System.Data.Linq.SqlClient.SqlMethods.Like(...), который LINQ-to-SQL сопоставляется с LIKESQL Server.


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

2
это может работать с реализациями SQL, но не работает со стандартной коллекцией объектов
Крис МакГрат,

13

Что ж ... иногда это может быть неудобно использовать Contains, StartsWithили EndsWithособенно при поиске состояния определения значения, LIKEнапример, переданное значение "%" требует от разработчика использовать 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?
Ник

9

В собственном 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 () для «оценки клиента» - что я m уже работает. Обратите внимание, что в EF Core он перемещен вEF.Functions.Like()
Auspex

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 05

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

Это работает как "КАК" SQL ...


8
нет ... нет, это не так, он работает только как LIKE 'term%', который далеко не работает как оператор Like в целом и не поддерживает подстановочные знаки
Крис МакГрат,

3

Такой простой

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

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


3

В идеале вы должны использовать StartWithили EndWith.

Вот пример:

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

return lstPerson;

2

Вы можете вызвать единственный метод с предикатом:

var portCode = Database.DischargePorts
                   .Single(p => p.PortName.Contains("BALTIMORE"))
                   .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;
        }
    }


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

0

Как расширение Linq / SQL

Класс LikeExtension

Протестировано в .NET 5

 public static class LikeExtension {

    private static string ColumnDataBase<TEntity, TKey>(IModel model, Expression<Func<TEntity, TKey>> predicate) where TEntity : class {

        ITable table = model
            .GetRelationalModel()
            .Tables
            .First(f => f
                .EntityTypeMappings
                .First()
                .EntityType == model
                .FindEntityType(predicate
                    .Parameters
                    .First()
                .Type
            ));

        string column = (predicate.Body as MemberExpression).Member.Name;
        string columnDataBase = table.Columns.First(f => f.PropertyMappings.Count(f2 => f2.Property.Name == column) > 0).Name;

        return columnDataBase;

    }

    public static IQueryable<TEntity> Like<TEntity, TKey>(this DbContext context, Expression<Func<TEntity, TKey>> predicate, string text) where TEntity : class {

        string columnDataBase = ColumnDataBase(context.Model, predicate);
        return context.Set<TEntity>().FromSqlRaw(context.Set<TEntity>().ToQueryString() + " WHERE [" + columnDataBase + "] LIKE {0}", text);

    }

    public static async Task<IEnumerable<TEntity>> LikeAsync<TEntity, TKey>(this DbContext context, Expression<Func<TEntity, TKey>> predicate, string text, CancellationToken cancellationToken) where TEntity : class {

        string columnDataBase = ColumnDataBase(context.Model, predicate);
        return await context.Set<TEntity>().FromSqlRaw(context.Set<TEntity>().ToQueryString() + " WHERE [" + columnDataBase + "] LIKE {0}", text).ToListAsync(cancellationToken);

    }

    public static async Task<IEnumerable<TEntity>> LikeAsync<TEntity, TKey>(this IQueryable<TEntity> query, Expression<Func<TEntity, TKey>> predicate, string text, CancellationToken cancellationToken) where TEntity : class {

        DbSet<TEntity> entities = query as DbSet<TEntity>;
        string columnDataBase = ColumnDataBase(entities.EntityType.Model, predicate);
        return await entities.FromSqlRaw(query.ToQueryString() + " WHERE [" + columnDataBase + "] LIKE {0}", text).ToListAsync(cancellationToken);

    }

    public static IQueryable<TEntity> Like<TEntity, TKey>(this IQueryable<TEntity> query, Expression<Func<TEntity, TKey>> predicate, string text) where TEntity : class {

        DbSet<TEntity> entities = query as DbSet<TEntity>;
        string columnDataBase = ColumnDataBase(entities.EntityType.Model, predicate);
        return entities.FromSqlRaw(query.ToQueryString() + " WHERE [" + columnDataBase + "] LIKE {0}", text);

    }

}

Репозиторий

    public async Task<IEnumerable<TEntity>> LikeAsync<TKey>(Expression<Func<TEntity, TKey>> predicate, string text, CancellationToken cancellationToken) {

        return await context.LikeAsync(predicate, text, cancellationToken);

    }

    public IQueryable<TEntity> Like<TKey>(Expression<Func<TEntity, TKey>> predicate, string text) {

        return context.Like(predicate, text);

    }

Использовать

 IQueryable<CountryEntity> result = countryRepository
     .Like(k => k.Name, "%Bra[sz]il%") /*Use Sync*/
     .Where(w => w.DateRegister < DateTime.Now) /*Example*/
     .Take(10); /*Example*/

Или

 IEnumerable<CountryEntity> result = await countryRepository
     .LikeAsync(k => k.Name, "%Bra[sz]il%", cancellationToken); /*Use Async*/

Или

 IQueryable<CountryEntity> result = context.Countries
     .Like(k => k.Name, "%Bra[sz]il%")
     .Where(w => w.Name != null); /*Example*/

Или

 List<CountryEntity> result2 = await context.Countries
     .Like(k => k.Name, "%Bra[sz]il%")
     .Where(w => w.Name != null) /*Example*/
     .ToListAsync(); /*Use Async*/

Или

 IEnumerable<CountryEntity> result3 = await context.Countries
     .Where(w => w.Name != null)
     .LikeAsync(k => k.Name, "%Bra[sz]il%", cancellationToken); /*Use Async*/
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.