2011-04-14 42 views
12

我試圖在LINQ to Objects中模擬LIKE運營商。在這裏我的代碼:像運營商在LINQ到對象

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運算符類似的東西?

回答

18

我不知道一個容易存在的,但如果你熟悉正則表達式,你可以寫你自己:

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; 
+0

哇!它最令人敬畏的答案比別人!非常感謝你 – 2011-04-14 13:20:07

6

這段代碼將模仿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); 

編輯 的即興如果有人偶然想要使用這個代碼,它轉換和編譯正則表達式一次,而不是每個項目,從上面的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(); 
    } 
} 
+0

這是最好的答案,謝謝! – 2013-05-16 14:55:17

+1

我實際上應該注意到,我個人不贊成IsSqlLikeMatch的實現。幾年前在互聯網上發現它。我可以找到的最佳歸屬是:http://bytes.com/topic/c-sharp/answers/253519-using-regex-create-sqls-like-like-function我認爲這是原始的 – dkackman 2013-05-20 17:24:10

5

你必須使用正則表達式的模式,然後使用擴展方法Where迭代,找到匹配。

所以,你的代碼應該結束這樣的:

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

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

如果你不熟悉正則表達式,這寫着:(。*)

首先0個或更多字符其次INE(ine)然後0個或更多字符(。*) then和e(e)Ë應該是字符串($)

1

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有一個名爲像方法,可以在一個LINQ用於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

謝謝你的回答!但你是關於什麼的?不幸的是,我們不能使用LINQ中的SqlMethods.Like作爲我的問題中提到的對象 – 2011-04-14 13:17:56

+0

不知道爲什麼這被拒絕了它適用於我。 – awiebe 2012-08-19 04:05:32

+0

@awiebe:你是否強制這與LINQ to OBjects一起工作? – 2012-08-28 13:29:27

0

我不知道它是否存在,但這是一個使用我所做的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; 

      }