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?

shamim
fonte
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.

Jon Skeet
fonte
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.

Marc Gravell
fonte
@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;
    }
}
Adobrzyc
fonte
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;
BitKFu
fonte
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.

Marat Batalandabad
fonte
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 ...

user1930698
fonte
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

Yannick Turbang
fonte
2

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

var portCode = Database.DischargePorts
                   .Single(p => p.PortName.Contains("BALTIMORE"))
                   .PortCode;
Zebi
fonte
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;
Eduardo Romero Marin
fonte
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
fonte
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;
NoBrend s
fonte
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
fonte
considere dar uma olhada em como escrever uma boa resposta
Aissani Abdelillah