Operador LIKE en LINQ

89

¿Hay alguna forma de comparar cadenas en una expresión C # LINQ similar al LIKEoperador de SQL ?

Supongamos que tengo una lista de cadenas. En esta lista quiero buscar una cadena. En SQL, podría escribir:

SELECT * FROM DischargePort WHERE PortName LIKE '%BALTIMORE%'

En lugar de lo anterior, la consulta quiere una sintaxis linq.

using System.Text.RegularExpressions;
…

var regex = new Regex(sDischargePort, RegexOptions.IgnoreCase);
var sPortCode = Database.DischargePorts
                .Where(p => regex.IsMatch(p.PortName))
                .Single().PortCode;

Mi sintaxis LINQ anterior no funciona. ¿Qué me he equivocado?

farsa
fuente
1
Esta consulta esencialmente funcionó para mí cuando la puso en su lugar. Pero, estoy usando el controlador MongoDb Linq y hay diferencias de implementación en cada proveedor Linq ... de todos modos, gracias.
Mark Ewer
Esta es la mejor solución que he encontrado para LINQ. Gracias. - @ Pranay-Rana
Abhishek Tomar

Respuestas:

143

Normalmente usa String.StartsWith/ EndsWith/ Contains. Por ejemplo:

var portCode = Database.DischargePorts
                       .Where(p => p.PortName.Contains("BALTIMORE"))
                       .Single()
                       .PortCode;

Sin embargo, no sé si hay una forma de hacer expresiones regulares adecuadas a través de LINQ to SQL. (Tenga en cuenta que realmente depende del proveedor que esté utilizando; estaría bien en LINQ to Objects; es una cuestión de si el proveedor puede convertir la llamada a su formato de consulta nativo, por ejemplo, SQL).

EDITAR: Como dice BitKFu, Singledebe usarse cuando espera exactamente un resultado, cuando es un error para que ese no sea el caso. Opciones de SingleOrDefault, FirstOrDefaulto Firstdeberían usarse dependiendo exactamente de lo que se espera.

Jon Skeet
fuente
amigo, pero hay un problema, mi lista contiene "BALTIMORE", y mi parámetro de comparación dado es "BALTIMORE [MD], US". La sintaxis anterior no se pudo seleccionar.
Shamim
2
eche un vistazo a mi declaración a continuación, podría provenir del método Single (). Es mejor usar FirstOrDefault ()
BitKFu
3
@shamim: ¿Entonces sus datos no contienen la cadena que está buscando? ¿Cómo esperaría que funcione incluso en SQL?
Jon Skeet
En SQL, es posible que no obtenga un conjunto de resultados; en C #, recibirá una excepción. Lo cual es ligeramente diferente, en lugar de no obtener resultados. Por eso recomendé usar FirstOrDefault.
BitKFu
@BitKFu desde un punto de partida de Single(), SingleOrDefault()sería mi próximo paso, a menos que comprendamos el contexto completo ...
Marc Gravell
34

¿Regex? No. Pero para esa consulta puedes usar:

 string filter = "BALTIMORE";
 (blah) .Where(row => row.PortName.Contains(filter)) (blah)

Si realmente desea SQL LIKE, puede usar System.Data.Linq.SqlClient.SqlMethods.Like(...), a qué asigna LINQ-to-SQL LIKEen SQL Server.

Marc Gravell
fuente
@Maslow, me temo que no es mi área de especialización, pero no creo que haya una forma limpia y agradable de asignar eso a todas las implementaciones de EF, así que ...
Marc Gravell
2
esto puede funcionar en implementaciones de SQL pero no funciona con una colección de objetos estándar
Chris McGrath
13

Bueno ... a veces puede ser incómodo de usar Contains, StartsWitho EndsWithespecialmente cuando se busca un valor de determinación, LIKEpor ejemplo, el 'valor%' pasado requiere que el desarrollador use la StartsWithfunción en la expresión. Entonces decidí escribir extensión para IQueryableobjetos.

Uso

// numbers: 11-000-00, 00-111-00, 00-000-11

var data1 = parts.Like(p => p.Number, "%11%");
// result: 11-000-00, 00-111-00, 00-000-11

var data2 = parts.Like(p => p.Number, "11%");
// result: 11-000-00

var data3 = parts.Like(p => p.Number, "%11");
// result: 00-000-11

Código

public static class LinqEx
{
    private static readonly MethodInfo ContainsMethod = typeof(string).GetMethod("Contains");
    private static readonly MethodInfo StartsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
    private static readonly MethodInfo EndsWithMethod = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });

    public static Expression<Func<TSource, bool>> LikeExpression<TSource, TMember>(Expression<Func<TSource, TMember>> property, string value)
    {
        var param = Expression.Parameter(typeof(TSource), "t");
        var propertyInfo = GetPropertyInfo(property);
        var member = Expression.Property(param, propertyInfo.Name);

        var startWith = value.StartsWith("%");
        var endsWith = value.EndsWith("%");

        if (startWith)
            value = value.Remove(0, 1);

        if (endsWith)
            value = value.Remove(value.Length - 1, 1);

        var constant = Expression.Constant(value);
        Expression exp;

        if (endsWith && startWith)
        {
            exp = Expression.Call(member, ContainsMethod, constant);
        }
        else if (startWith) 
        {
            exp = Expression.Call(member, EndsWithMethod, constant);
        }
        else if (endsWith)
        {
            exp = Expression.Call(member, StartsWithMethod, constant);
        }
        else
        {
            exp = Expression.Equal(member, constant);
        }

        return Expression.Lambda<Func<TSource, bool>>(exp, param);
    }

    public static IQueryable<TSource> Like<TSource, TMember>(this IQueryable<TSource> source, Expression<Func<TSource, TMember>> parameter, string value)
    {
        return source.Where(LikeExpression(parameter, value));
    }

    private static PropertyInfo GetPropertyInfo(Expression expression)
    {
        var lambda = expression as LambdaExpression;
        if (lambda == null)
            throw new ArgumentNullException("expression");

        MemberExpression memberExpr = null;

        switch (lambda.Body.NodeType)
        {
            case ExpressionType.Convert:
                memberExpr = ((UnaryExpression)lambda.Body).Operand as MemberExpression;
                break;
            case ExpressionType.MemberAccess:
                memberExpr = lambda.Body as MemberExpression;
                break;
        }

        if (memberExpr == null)
            throw new InvalidOperationException("Specified expression is invalid. Unable to determine property info from expression.");


        var output = memberExpr.Member as PropertyInfo;

        if (output == null)
            throw new InvalidOperationException("Specified expression is invalid. Unable to determine property info from expression.");

        return output;
    }
}
adobrzyc
fuente
¿Tiene una versión que funcione IEnumerable?
Nicke Manarin
8

Como ya mencionaron Jon Skeet y Marc Gravell, simplemente puede tomar una condición contiene. Pero en el caso de su consulta similar, es muy peligroso tomar una declaración Single (), porque eso implica que solo encuentra 1 resultado. En caso de obtener más resultados, recibirá una buena excepción :)

Entonces preferiría usar FirstOrDefault () en lugar de Single ():

var first = Database.DischargePorts.FirstOrDefault(p => p.PortName.Contains("BALTIMORE"));
var portcode = first != null ? first.PortCode : string.Empty;
BitKFu
fuente
si es nuestra expectativa afirmada que hay exactamente una coincidencia, Single no es "peligroso" - es "correcto". Todo se reduce a lo que afirmamos sobre los datos ... "cualquier número", "al menos uno", "como máximo uno", "exactamente uno", etc.
Marc Gravell
3
dependiendo del contexto, puede ser ... depende completamente de la expectativa de la consulta
Marc Gravell
¿Qué pasa con una búsqueda "vacía" o "%"? ¿Podría esto manejar "B", "BALT" y "" (es decir, conseguirme todo)?
BlueChippy
8

En LINQ nativo, puede utilizar una combinación de Contains/StartsWith/EndsWitho RegExp.

En el método de uso de LINQ2SQL SqlMethods.Like()

    from i in db.myTable
    where SqlMethods.Like(i.field, "tra%ata")
    select i

agregue Ensamblado: System.Data.Linq (en System.Data.Linq.dll) para usar esta función.

Marat Batalandabad
fuente
Entiendo que el OP en realidad no decía Linq2SQL, pero parecía implícito. La razón por la que estoy aquí es que StartsWith(), Contains(), etc, qué no trabajo con Linq2SQL (al menos me sale "La expresión LINQ ... no se podría traducir ..." y una instrucción para el uso ToList () para "evaluación del cliente", lo que yo' . m Nota haciendo ya, en EF Core, se trasladó aEF.Functions.Like()
Auspex
3
  .Where(e => e.Value.StartsWith("BALTIMORE"))

Esto funciona como "LIKE" de SQL ...

user1930698
fuente
8
no ... no, no, solo funciona como LIKE 'term%', que está lejos de funcionar como el operador like en su conjunto y no admite comodines
Chris McGrath
3

Tan simple como esto

string[] users = new string[] {"Paul","Steve","Annick","Yannick"};    
var result = from u in users where u.Contains("nn") select u;

Resultado -> Annick, Yannick

Yannick Turbang
fuente
2

Puede llamar al método único con un predicado:

var portCode = Database.DischargePorts
                   .Single(p => p.PortName.Contains("BALTIMORE"))
                   .PortCode;
Zebi
fuente
2

Lo ideal sería utilizar StartWitho EndWith.

Aquí hay un ejemplo:

DataContext  dc = new DCGeneral();
List<Person> lstPerson= dc.GetTable<Person>().StartWith(c=> c.strNombre).ToList();

return lstPerson;
Eduardo Romero Marín
fuente
0
   public static class StringEx
    {
        public static bool Contains(this String str, string[] Arr, StringComparison comp)
        {
            if (Arr != null)
            {
                foreach (string s in Arr)
                {
                    if (str.IndexOf(s, comp)>=0)
                    { return true; }
                }
            }

            return false;
        }

        public static bool Contains(this String str,string[] Arr)
        {
            if (Arr != null)
            {
                foreach (string s in Arr)
                {
                    if (str.Contains(s))
                    { return true; }
                }
            }

            return false;
        }
    }


var portCode = Database.DischargePorts
                   .Single(p => p.PortName.Contains( new string[] {"BALTIMORE"},  StringComparison.CurrentCultureIgnoreCase) ))
                   .PortCode;
NoBrend s
fuente
0

Simplemente agregue a los métodos de extensión de objetos de cadena.

public static class StringEx
{
    public static bool Contains(this String str, string[] Arr, StringComparison comp)
    {
        if (Arr != null)
        {
            foreach (string s in Arr)
            {
                if (str.IndexOf(s, comp)>=0)
                { return true; }
            }
        }

        return false;
    }

    public static bool Contains(this String str,string[] Arr)
    {
        if (Arr != null)
        {
            foreach (string s in Arr)
            {
                if (str.Contains(s))
                { return true; }
            }
        }

        return false;
    }
}

uso:

use namespase that contains this class;

var sPortCode = Database.DischargePorts
            .Where(p => p.PortName.Contains(new string [] {"BALTIMORE"},  StringComparison.CurrentCultureIgnoreCase) )
            .Single().PortCode;
NoBrend s
fuente
0
List<Categories> categoriess;
        private void Buscar()
        {
            try
            {
                categoriess = Contexto.Categories.ToList();
                categoriess = categoriess.Where(n => n.CategoryID >= Convert.ToInt32(txtCatID.Text) && n.CategoryID <= Convert.ToInt32(txtCatID1.Text) && (n.CategoryName.Contains(txtCatName.Text)) ).ToList();
Eber Camacho
fuente
considere echar un vistazo a cómo escribir una buena respuesta
Aissani Abdelillah
0

@adobrzyc tenía esta gran LIKEfunción personalizada , solo quería compartir la IEnumerableversión.

public static class LinqEx
{
    private static readonly MethodInfo ContainsMethod = typeof(string).GetMethod("Contains");
    private static readonly MethodInfo StartsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
    private static readonly MethodInfo EndsWithMethod = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });

    private static Func<TSource, bool> LikeExpression<TSource, TMember>(Expression<Func<TSource, TMember>> property, string value)
    {
        var param = Expression.Parameter(typeof(TSource), "t");
        var propertyInfo = GetPropertyInfo(property);
        var member = Expression.Property(param, propertyInfo.Name);

        var startWith = value.StartsWith("%");
        var endsWith = value.EndsWith("%");

        if (startWith)
            value = value.Remove(0, 1);

        if (endsWith)
            value = value.Remove(value.Length - 1, 1);

        var constant = Expression.Constant(value);
        Expression exp;

        if (endsWith && startWith)
        {
            exp = Expression.Call(member, ContainsMethod, constant);
        }
        else if (startWith)
        {
            exp = Expression.Call(member, EndsWithMethod, constant);
        }
        else if (endsWith)
        {
            exp = Expression.Call(member, StartsWithMethod, constant);
        }
        else
        {
            exp = Expression.Equal(member, constant);
        }

        return Expression.Lambda<Func<TSource, bool>>(exp, param).Compile();
    }

    public static IEnumerable<TSource> Like<TSource, TMember>(this IEnumerable<TSource> source, Expression<Func<TSource, TMember>> parameter, string value)
    {
        return source.Where(LikeExpression(parameter, value));
    }


    private static PropertyInfo GetPropertyInfo(Expression expression)
    {
        var lambda = expression as LambdaExpression;
        if (lambda == null)
            throw new ArgumentNullException("expression");

        MemberExpression memberExpr = null;

        switch (lambda.Body.NodeType)
        {
            case ExpressionType.Convert:
                memberExpr = ((UnaryExpression)lambda.Body).Operand as MemberExpression;
                break;
            case ExpressionType.MemberAccess:
                memberExpr = lambda.Body as MemberExpression;
                break;
        }

        if (memberExpr == null)
            throw new InvalidOperationException("Specified expression is invalid. Unable to determine property info from expression.");


        var output = memberExpr.Member as PropertyInfo;

        if (output == null)
            throw new InvalidOperationException("Specified expression is invalid. Unable to determine property info from expression.");

        return output;
    }
}
Steve
fuente