Оператор Like в LINQ to Objects - PullRequest
12 голосов
/ 14 апреля 2011

Я пытаюсь эмулировать оператор LIKE в LINQ to Objects.Вот мой код:

List<string> list = new List<string>();
list.Add("line one");
list.Add("line two");
list.Add("line three");
list.Add("line four");
list.Add("line five");
list.Add("line six");
list.Add("line seven");
list.Add("line eight");
list.Add("line nine");
list.Add("line ten");

string pattern = "%ine%e";

var res = from i in list
            where System.Data.Linq.SqlClient.SqlMethods.Like(i, pattern)
              select i;

Я не получил результат, потому что System.Data.Linq.SqlClient.SqlMethods.Like предназначен только для перевода на SQL.

Есть ли что-то похожее на оператор sql LIKE, существующее в LINQ дляМир предметов?

Ответы [ 5 ]

18 голосов
/ 14 апреля 2011

Я не знаю ни одного, который легко существует, но если вы знакомы с регулярными выражениями, вы можете написать свой собственный:

using System;
using System.Text.RegularExpressions;

public static class MyExtensions
{
    public static bool Like(this string s, string pattern, RegexOptions options = RegexOptions.IgnoreCase)
    {
        return Regex.IsMatch(s, pattern, options);
    }
}

А потом в вашем коде:

string pattern = ".*ine.*e";
var res = from i in list
    where i.Like(pattern)
    select i;
7 голосов
/ 14 апреля 2011

Этот фрагмент будет имитировать поведение и синтаксис Sql LIKE. Вы можете заключить его в лямбда-код или собственный метод расширения для использования в операторе Linq:

public static bool IsSqlLikeMatch(string input, string pattern)
{
   /* Turn "off" all regular expression related syntax in
    * the pattern string. */
   pattern = Regex.Escape(pattern);

   /* Replace the SQL LIKE wildcard metacharacters with the
    * equivalent regular expression metacharacters. */
   pattern = pattern.Replace("%", ".*?").Replace("_", ".");

   /* The previous call to Regex.Escape actually turned off
    * too many metacharacters, i.e. those which are recognized by
    * both the regular expression engine and the SQL LIKE
    * statement ([...] and [^...]). Those metacharacters have
    * to be manually unescaped here. */
   pattern = pattern.Replace(@"\[", "[").Replace(@"\]", "]").Replace(@"\^", "^");

   return Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
}

Метод расширения со сгущением, который будет работать как IEnumerable<T>.Where метод:

public static IEnumerable<T> Like<T>(this IEnumerable<T> source, Func<T, string> selector, string pattern)
{
   return source.Where(t => IsSqlLikeMatch(selector(t), pattern));
}

Что, в свою очередь, позволит вам отформатировать ваше утверждение следующим образом:

string pattern = "%ine%e";
var res = list.Like(s => s, pattern);

EDIT Улучшенная реализация, если кто-то наткнется и захочет использовать этот код. Вместо этого он конвертирует и компилирует регулярное выражение один раз для каждого элемента, а преобразование из LIKE в регулярное выражение имеет некоторые ошибки.

public static class LikeExtension
{
    public static IEnumerable<T> Like<T>(this IEnumerable<T> source, Func<T, string> selector, string pattern)
    {
        var regex = new Regex(ConvertLikeToRegex(pattern), RegexOptions.IgnoreCase);
        return source.Where(t => IsRegexMatch(selector(t), regex));
    }

    static bool IsRegexMatch(string input, Regex regex)
    {
        if (input == null)
            return false;

        return regex.IsMatch(input);
    }

    static string ConvertLikeToRegex(string pattern)
    {
        StringBuilder builder = new StringBuilder();
        // Turn "off" all regular expression related syntax in the pattern string
        // and add regex begining of and end of line tokens so '%abc' and 'abc%' work as expected
        builder.Append("^").Append(Regex.Escape(pattern)).Append("$");

        /* Replace the SQL LIKE wildcard metacharacters with the
        * equivalent regular expression metacharacters. */
        builder.Replace("%", ".*").Replace("_", ".");

        /* The previous call to Regex.Escape actually turned off
        * too many metacharacters, i.e. those which are recognized by
        * both the regular expression engine and the SQL LIKE
        * statement ([...] and [^...]). Those metacharacters have
        * to be manually unescaped here. */
        builder.Replace(@"\[", "[").Replace(@"\]", "]").Replace(@"\^", "^");

        // put SQL LIKE wildcard literals back
        builder.Replace("[.*]", "[%]").Replace("[.]", "[_]");

        return builder.ToString();
    }
}
5 голосов
/ 14 апреля 2011

Вы должны использовать Regex для шаблона, а затем использовать метод расширения Where для итерации и поиска совпадений.

Итак, ваш код должен выглядеть так:

string pattern = @".*ine.*e$";

var res = list.Where( e => Regex.IsMatch( e, pattern));

Если вы не знакомы с Regex, это звучит так:

Первые 0 или более символов (. *) , за которыми следует ine (ine) , затем 0 или более символов (. *) затем и e (e) , а e должен быть концом строки ($)

1 голос
/ 14 апреля 2011

1.Использование String.StartsWith или String.Endswith

Написание следующего запроса:

var query = from c in ctx.Customers

            where c.City.StartsWith("Lo")

            select c;

will generate this SQL statement:
SELECT CustomerID, CompanyName, ...
FROM    dbo.Customers
WHERE  City LIKE [Lo%]

, что именно то, что мы хотели.То же самое относится и к String.EndsWith.

Но что мы хотим запросить у клиента с названием города, например, "L_n%"?(начинается с заглавной буквы 'L', чем какой-либо символ, чем 'n' и чем остальная часть имени).Используя запрос

var query = from c in ctx.Customers

            where c.City.StartsWith("L") && c.City.Contains("n")

            select c;

generates the statement:
SELECT CustomerID, CompanyName, ...
FROM    dbo.Customers
WHERE  City LIKE [L%]
AND      City LIKE [%n%]

, что не совсем то, что мы хотели, а также немного сложнее.

2.Используя метод SqlMethods.Like

Копая пространство имен System.Data.Linq.SqlClient, я нашел небольшой вспомогательный класс под названием SqlMethods, который может быть очень полезен в таких сценариях.SqlMethods имеет метод с именем Like, который можно использовать в запросе Linq to SQL:

var query = from c in ctx.Customers

            where SqlMethods.Like(c.City, "L_n%")

            select c;

Этот метод получает проверяемое строковое выражение (город клиента в этом примере) и шаблоны, для проверки которыхпредоставляется аналогично тому, как вы пишете предложение LIKE в SQL.

При использовании вышеупомянутого запроса генерируется требуемый оператор SQL:

SELECT CustomerID, CompanyName, ...
FROM    dbo.Customers
WHERE  City LIKE [L_n%]

Источник: http://blogs.microsoft.co.il/blogs/bursteg/archive/2007/10/16/linq-to-sql-like-operator.aspx

0 голосов
/ 26 февраля 2015

Я не знаю, существует ли он, но вот реализация метода расширения, использующего алгоритм Кнута-Морриса-Пратта, который я сделал.

public static IEnumerable<T> Like<T>(this IEnumerable<T> lista, Func<T, string> type, string pattern)
            {

                int[] pf = prefixFunction(pattern);

                foreach (T e in lista)
                {
                    if (patternKMP(pattern, type(e), pf))
                        yield return e;
                }

            }

            private static int[] prefixFunction(string p)
            {


                int[] pf = new int[p.Length];
                int k = pf[0] = -1;


                for (int i = 1; i < p.Length; i++)
                {
                    while (k > -1 && p[k + 1] != p[i])
                        k = pf[k];

                    pf[i] = (p[k + 1] == p[i]) ? ++k : k;
                }
                return pf;

            }

            private static bool patternKMP(string p, string t, int[] pf)
            {

                for (int i = 0, k = -1; i < t.Length; i++)
                {

                    while (k > -1 && p[k + 1] != t[i])
                        k = pf[k];

                    if (p[k + 1] == t[i])
                        k++;

                    if (k == p.Length - 1)
                        return true;    
                }

                return false;

            }
...