LINQ to Objects 中的 Like 运算符

发布于 2024-11-01 20:04:14 字数 676 浏览 6 评论 0原文

我正在尝试模拟 LINQ to Objects 中的 LIKE 运算符。这是我的代码:

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。

LINQ to Objects 世界中是否存在类似于 sql LIKE 运算符的内容?

I'm trying to emulate the LIKE operator in LINQ to Objects. Here my code:

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;

It did not get me result because of System.Data.Linq.SqlClient.SqlMethods.Like is only for translation into SQL.

Does anything similar to sql LIKE operator exists in LINQ to Objects world?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

↘人皮目录ツ 2024-11-08 20:04:14

我不知道有一个现成的,但如果您熟悉正则表达式,您可以编写自己的:

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;

I don't know of one that readily exists, but if you're familiar with regular expressions, you can write your own:

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

And then in your code:

string pattern = ".*ine.*e";
var res = from i in list
    where i.Like(pattern)
    select i;
温柔一刀 2024-11-08 20:04:14

此代码片段将模仿 Sql LIKE 的行为和语法。您可以将其包装到您自己的 lambda 或扩展方法中,以便在 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.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);

编辑
一个改进的实现,如果有人偶然发现并想要使用此代码。它为每个项目转换并编译一次正则表达式,并且上面从 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();
    }
}

This snippet will mimic the behavior and syntax of Sql LIKE. You can wrap it up into a lambda or extension method of your own for use within a Linq statement:

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

A roughed together extension method that would work like IEnumerable<T>.Where method:

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

Which would in turn allow you to format your statement like so:

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

EDIT
An improved implementation, should anyone stumble upon and want to use this code. It converts and compiles the regex once instead for each item and the conversion from LIKE to regex above has some bugs.

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();
    }
}
小草泠泠 2024-11-08 20:04:14

您必须使用正则表达式作为模式,然后使用扩展方法 Where 来迭代并查找匹配项。

所以你的代码最终应该是这样的:

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

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

如果你不熟悉正则表达式,这会显示:

前 0 个或多个字符 (.*) 后跟 ine (ine ),然后 0 个或多个字符 (.*),然后 e (e),以及 e 应该是字符串的结尾($)

You have to use Regex for the pattern, and then use the extension method Where to iterate and find the matches.

So your code should end up like this:

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

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

If you are unfamiliar with Regex, this reads:

First 0 or more characters (.*) followed by ine (ine) then 0 or more characters (.*) then and e (e), and the e should be the end of the string ($)

美羊羊 2024-11-08 20:04:14

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;

此方法获取要检查的字符串表达式(本例中为客户的城市)以及要测试的模式,其提供方式与您提供的方式相同在 SQL 中编写 LIKE 子句。

使用上述查询生成所需的 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

1. Using String.StartsWith or String.Endswith

Writing the following query:

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%]

which is exactly what we wanted. Same goes with String.EndsWith.

But, what is we want to query the customer with city name like "L_n%"? (starts with a Capital 'L', than some character, than 'n' and than the rest of the name). Using the query

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%]

which is not exactly what we wanted, and a little more complicated as well.

2. Using SqlMethods.Like method

Digging into System.Data.Linq.SqlClient namespace, I found a little helper class called SqlMethods, which can be very usefull in such scenarios. SqlMethods has a method called Like, that can be used in a Linq to SQL query:

var query = from c in ctx.Customers

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

            select c;

This method gets the string expression to check (the customer's city in this example) and the patterns to test against which is provided in the same way you'd write a LIKE clause in SQL.

Using the above query generated the required SQL statement:

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

Source: http://blogs.microsoft.co.il/blogs/bursteg/archive/2007/10/16/linq-to-sql-like-operator.aspx

染年凉城似染瑾 2024-11-08 20:04:14

我不知道它是否存在,但这里是使用我制作的 Knuth-Morris-Pratt 算法的扩展方法的实现。

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;

            }

I don't know if it exists, but here is an implementation of an extension method using Knuth-Morris-Pratt algorithm I made.

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;

            }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文