Operador LIKE no LINQ


88

Existe alguma maneira de comparar strings em uma expressão C # LINQ semelhante ao LIKEoperador SQL ?

Suponha que eu tenha uma lista de cordas. Nesta lista, desejo pesquisar uma string. Em SQL, eu poderia escrever:

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

Em vez do acima, a consulta deseja uma sintaxe linq.

using System.Text.RegularExpressions;
…

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

Minha sintaxe LINQ acima não funciona. O que eu entendi de errado?


1
Essa consulta funcionou essencialmente para mim conforme você a colocava. Mas, estou usando o driver MongoDb Linq e há diferenças de implementação em cada provedor Linq ... de qualquer maneira, obrigado.
— Mark Ewer

Esta é a melhor solução que encontrei para como no LINQ. Obrigado. - @ Pranay-Rana
— Abhishek Tomar

Respostas:


140

Normalmente você usa String.StartsWith/ EndsWith/ Contains. Por exemplo:

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

Não sei se existe uma maneira de fazer expressões regulares adequadas por meio do LINQ to SQL. (Observe que realmente depende de qual provedor você está usando - seria ótimo no LINQ to Objects; é uma questão de o provedor poder converter a chamada em seu formato de consulta nativo, por exemplo, SQL.)

EDIT: Como diz o BitKFu, Singledeve ser usado quando você espera exatamente um resultado - quando é um erro não ser o caso. Opções de SingleOrDefault, FirstOrDefaultou Firstdeve ser usado dependendo exatamente o que se espera.


amigo, mas há um problema: Minha lista contém "BALTIMORE" e meu parâmetro de comparação fornecido é "BALTIMORE [MD], US". Acima da sintaxe, falha ao selecionar.
— shamim,

2
dê uma olhada na minha declaração abaixo, ela pode vir do método Single (). É melhor usar FirstOrDefault ()
— BitKFu

3
@shamim: Então seus dados não contêm a string que você está procurando? Como você espera que isso funcione mesmo no SQL?
— Jon Skeet,

No SQL, você pode não obter nenhum conjunto de resultados - no C #, você receberá uma exceção. O que é um pouco diferente, em vez de nenhum resultado. É por isso que recomendei usar FirstOrDefault.
— BitKFu

@BitKFu do ponto de partida de Single(), SingleOrDefault()seria meu próximo passo, a menos que entendamos todo o contexto ...
— Marc Gravell

34

Regex? não. Mas para essa consulta, você pode apenas usar:

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

Se você realmente deseja SQL LIKE, pode usar System.Data.Linq.SqlClient.SqlMethods.Like(...), para qual LINQ para SQL mapeia LIKEno SQL Server.


@Maslow - não é minha área de especialização, receio - mas não acredito que haja uma maneira limpa e agradável de mapear isso para todas as implementações EF, então ... não.
— Marc Gravell

2
isso pode funcionar em implementações de SQL, mas não funciona com uma coleção de objetos padrão
— Chris McGrath

13

Bem ... às vezes pode ser desconfortável de usar Contains, StartsWithou EndsWithespecialmente ao pesquisar o valor, determinar a LIKEinstrução, por exemplo, o 'valor%' passado requer do desenvolvedor para usar a StartsWithfunção na expressão. Então decidi escrever extensão 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;
    }
}

Você tem uma versão que funcione IEnumerable?
— Nicke Manarin

8

Como Jon Skeet e Marc Gravell já mencionaram, você pode simplesmente pegar uma condição contém. Mas no caso de sua consulta like, é muito perigoso usar uma instrução Single (), porque isso implica que você encontrará apenas 1 resultado. No caso de mais resultados, você receberá uma boa exceção :)

Então, eu preferiria usar FirstOrDefault () em vez de Single ():

var first = Database.DischargePorts.FirstOrDefault(p => p.PortName.Contains("BALTIMORE"));
var portcode = first != null ? first.PortCode : string.Empty;

se for nossa expectativa afirmada de que haja exatamente uma correspondência, Single não é "perigoso" - é "correto". Tudo se resume ao que afirmamos sobre os dados ... "qualquer número", "pelo menos um", "no máximo um", "exatamente um", etc
— Marc Gravell

3
dependendo do contexto, pode ser ... depende inteiramente da expectativa da consulta
— Marc Gravell

Que tal uma pesquisa "vazia" ou "%"? Isso poderia lidar com "B", "BALT" e "" (o que significa pegar tudo para mim)?
— BlueChippy

8

No LINQ nativo, você pode usar a combinação de Contains/StartsWith/EndsWithou RegExp.

No LINQ2SQL, use o método SqlMethods.Like()

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

adicione Assembly: System.Data.Linq (em System.Data.Linq.dll) para usar este recurso.


Eu entendo que o OP não dizia realmente Linq2SQL, mas parecia implícito. O motivo de estar aqui é que StartsWith(), Contains()etc, não funcionam com Linq2SQL (pelo menos recebo "A expressão LINQ ... não pôde ser traduzida ..." e uma instrução para usar ToList () para "avaliação de cliente" - que eu ' já estou fazendo. Observe, no EF Core, ele foi movido paraEF.Functions.Like()
— Auspex

3
  .Where(e => e.Value.StartsWith("BALTIMORE"))

Isso funciona como "LIKE" do SQL ...


8
não .. não, não funciona apenas como LIKE 'term%' que está longe de funcionar como o operador like como um todo e não suporta caracteres curinga
— Chris McGrath

3

Simples assim

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

Resultado -> Annick, Yannick


2

Você pode chamar o método único com um predicado:

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

2

Idealmente, você deve usar StartWithou EndWith.

Aqui está um exemplo:

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

return lstPerson;

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;

0

Basta adicionar métodos de extensão de objeto de string.

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;

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.