2011-04-14 13 views
12

Estoy tratando de emular el operador LIKE en LINQ to Objects. Aquí mi código:Operador Me gusta en 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; 

no llegó conmigo resultado debido System.Data.Linq.SqlClient.SqlMethods.Like es sólo para la traducción a SQL.

¿Existe algo similar al operador sql LIKE en el mundo LINQ to Objects?

Respuesta

18

no sé de uno que existe fácilmente, pero si usted está familiarizado con las expresiones regulares, puede escribir su propia:

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

Y luego en el código:

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

¡Guau! ¡Su respuesta más increíble que otras! Muchas gracias –

6

Este fragmento imitará el comportamiento y la sintaxis de Sql LIKE. Puede envolverlo en un método lambda o extensión de su propia para su uso dentro de una sentencia 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); 
} 

Un maltratado juntos método de extensión que funcionaría como IEnumerable<T>.Where método:

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

lo que a su gire permite dar formato a su estado de cuenta así:

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

EDITAR Una improvisación Implementación ed, si alguien tropieza con este código y desea utilizarlo. En lugar de eso, convierte y compila la expresión regular para cada elemento y la conversión de LIKE a regex anterior tiene algunos errores.

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

Esta es la mejor respuesta, gracias! –

+1

Debo notar que personalmente no me puedo atribuir el mérito de la implementación de IsSqlLikeMatch. Lo encontré en los interwebs hace años. La mejor atribución que puedo encontrar es: http://bytes.com/topic/c-sharp/answers/253519-using-regex-create-sqls-like-like-function Creo que es el original – dkackman

5

usted tiene que utilizar expresiones regulares para el patrón, y luego usar el método de extensión Where para recorrer y encontrar las coincidencias.

Así que el código debe terminar como esto:

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

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

Si no está familiarizado con las expresiones regulares, esto se lee: (. *)

Primera 0 o más caracteres seguidos por ine(ine) luego 0 o más caracteres (. *) luego y e(e) Y el correo debería ser el final de la cadena ($)

1

1. Usando String.StartsWith o String.Endswith

escribir la siguiente consulta:

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

que es exactamente lo que queríamos. Lo mismo ocurre con String.EndsWith.

Pero, ¿qué es lo que queremos preguntar al cliente con el nombre de la ciudad como "L_n%"? (comienza con una "L" mayúscula, que con un carácter, que con "n" y con el resto del nombre). Usando la consulta

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

que no es exactamente lo que queríamos, y un poco más complicado también.

2. Utilizando el método SqlMethods.Like

Excavando en System.Data.Linq.SqlClient espacio de nombres, me encontré con un poco de clase auxiliar llamada SqlMethods, que puede ser muy útil en tales escenarios. SqlMethods tiene un método llamado igual, que se puede utilizar en una consulta LINQ to SQL:

var query = from c in ctx.Customers 

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

      select c; 

Este método obtiene la expresión de cadena para comprobar (la ciudad del cliente en este ejemplo) y los patrones de la prueba contra el cual está provisto de la misma manera que escribirías una cláusula LIKE en SQL.

Uso de la consulta anterior genera la instrucción SQL requerido:

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

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

+1

Gracias por responder ! pero, ¿qué es usted? desafortunadamente no podemos usar SqlMethods.Like en LINQ a objetos como se menciona en mi pregunta –

+0

. No sé por qué esto fue votado que funciona para mí. – awiebe

+0

@awiebe: ¿Lo fuerza a trabajar con LINQ a OBjects? –

0

No sé si existe, pero aquí hay una implementación de un método de extensión usando el algoritmo Knuth-Morris-Pratt que hice.

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; 

      }