2011-04-14 5 views
12

J'essaie d'émuler l'opérateur LIKE dans LINQ to Objects. Voici mon code:Comme opérateur dans 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; 

Il ne m'a pas obtenir le résultat en raison de System.Data.Linq.SqlClient.SqlMethods.Like est seulement pour la traduction en SQL.

Est-ce que quelque chose de similaire à l'opérateur sql LIKE existe dans le monde de LINQ to Objects?

Répondre

18

Je ne sais pas d'un qui existe facilement, mais si vous êtes familier avec les expressions régulières, vous peut écrire votre propre:

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

Et puis dans votre code:

string pattern = ".*ine.*e"; 
var res = from i in list 
    where i.Like(pattern) 
    select i; 
+0

Wow! Sa réponse la plus impressionnante que les autres! Merci beaucoup –

6

Cet extrait imitera le comportement et la syntaxe de Sql LIKE. Vous pouvez envelopper dans une lambda ou une méthode extension de votre propre pour une utilisation dans une déclaration 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); 
} 

A malmené ensemble méthode d'extension qui fonctionnerait comme méthode 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)); 
} 

qui serait en tour, vous permettra de formater votre déclaration comme ceci:

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

EDIT Une impro mise en œuvre, si quelqu'un tombe sur et que vous voulez utiliser ce code. Il convertit et compile l'expression regex une fois pour chaque item et la conversion de LIKE en regex ci-dessus a quelques 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(); 
    } 
} 
+0

C'est la meilleure réponse, merci! –

+1

Je dois noter que je ne peux pas personnellement prendre le crédit pour la mise en œuvre de IsSqlLikeMatch. Trouvé sur les interwebs il y a des années. La meilleure attribution que je peux trouver est: http://bytes.com/topic/c-sharp/answers/253519-using-regex-create-sqls-like-like-function Je pense que c'est l'original – dkackman

5

Vous devez utiliser Regex pour le modèle, puis utilisez la méthode d'extension Where pour itérer et trouver les correspondances.

Donc, votre code devrait se terminer comme ceci:

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

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

Si vous n'êtes pas familier avec Regex, ce lit comme suit: (. *)

Premier 0 ou plusieurs caractères suivis par ine(ine) puis 0 ou plusieurs caractères (. *) puis et(e) Et le e devrait être la fin de la chaîne ($)

1

1. En utilisant String.StartsWith ou String.Endswith

Rédaction de la requête suivante:

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

ce qui est exactement ce que nous voulions. Il en va de même avec String.EndsWith.

Mais, de quoi voulons-nous interroger le client avec le nom de la ville comme "L_n%"? (commence par un «L» majuscule, que certains caractères, que «n» et que le reste du nom). En utilisant la requête

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

qui n'est pas exactement ce que nous voulions, et un peu plus compliqué aussi bien.

2. Utilisation de la méthode de SqlMethods.Like

Creuser dans System.Data.Linq.SqlClient espace de noms, je trouve une classe petite aide appelée SqlMethods, qui peut être très utile dans de tels scénarios. SqlMethods a une méthode appelée Comme, qui peut être utilisé dans une requête LINQ to SQL:

var query = from c in ctx.Customers 

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

      select c; 

Cette méthode est l'expression de chaîne pour vérifier (la ville du client dans cet exemple) et les modèles pour tester contre qui est prévu de la même manière que vous écrivez une clause LIKE en SQL.

Utilisation de la requête ci-dessus a généré l'instruction SQL requis:

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

+1

Merci de votre réponse ! mais de quoi parlez-vous? malheureusement, nous ne pouvons pas utiliser SqlMethods.Like dans LINQ à des objets comme mentionné dans ma question –

+0

Dunno pourquoi cela a été rejeté, il fonctionne pour moi. – awiebe

+0

@awiebe: Êtes-vous obligé de travailler avec LINQ to OBjects? –

0

Je ne sais pas s'il existe, mais voici une implémentation d'une méthode d'extension utilisant l'algorithme Knuth-Morris-Pratt que j'ai fait.

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; 

      }