Gosta do Operator no Entity Framework?


91

Estamos tentando implementar o operador "LIKE" no Entity Framework para nossas entidades com campos de string, mas não parece ser compatível. Alguém mais tentou fazer algo assim?

Esta postagem do blog resume o problema que estamos enfrentando. Poderíamos usar contém, mas isso apenas corresponde ao caso mais trivial de LIKE. A combinação de contains, startswith, endswith e indexof nos leva lá, mas requer uma tradução entre curingas padrão e Linq para código de entidades.


1
Vá para esta resposta se você já estiver usando EF 6.2.x. Para esta resposta, se você estiver usando EF Core 2.x
— CodeNotFound

Respostas:


35

Esta é uma postagem antiga agora, mas para quem está procurando a resposta, este link deve ajudar. Vá para esta resposta se você já estiver usando EF 6.2.x. Para esta resposta se você estiver usando EF Core 2.x

Versão curta:

Método SqlFunctions.PatIndex - retorna a posição inicial da primeira ocorrência de um padrão em uma expressão especificada, ou zeros se o padrão não for encontrado, em todos os tipos de dados de texto e caracteres válidos

Namespace: System.Data.Objects.SqlClient Assembly: System.Data.Entity (em System.Data.Entity.dll)

Uma pequena explicação também aparece neste tópico do fórum .


59
como a resposta aceita é aquela que leva a um fórum do MSDN que leva de volta a esta pergunta para a resposta abaixo ?
— Eonasdan,

A resposta foi usar o método SqlFunctions.PatIndex. O tópico do fórum vinculado era para fornecer um pouco mais de informações de "fundo".
— Yann Duran

A resposta abaixo é boa para padrões simples, mas se eu quiser dizer "WHERE Name LIKE 'abc [0-9]%'" ou algum outro padrão mais complexo, simplesmente usar Contains () não é suficiente.
— HotN

1
Duplique esta resposta mais antiga a esta pergunta. (Não de sua primeira parte, mas de sua solução alternativa.)
— Frédéric

154

Não sei nada sobre EF, na verdade, mas no LINQ to SQL você geralmente expressa uma cláusula LIKE usando String. Contém:

where entity.Name.Contains("xyz")

traduz para

WHERE Name LIKE '%xyz%'

(Use StartsWithe EndsWithpara outro comportamento.)

Não tenho certeza se isso é útil, porque não entendo o que você quer dizer quando diz que está tentando implementar LIKE. Se eu não entendi completamente, me avise e excluirei esta resposta :)


4
observe que "WHERE Name LIKE '% xyz%'" não poderá usar um índice, portanto, se a tabela for grande, ela pode não ter um desempenho tão bom ...
— Mitch Wheat

1
Bem, gostaríamos de poder combinar em blá * blá foo bar foo? Bar? Foo bar? e outros padrões complexos. Nossa abordagem atual é semelhante ao que você mencionou, converteríamos essas consultas em operações usando contains, indexof, startswith, endswith, etc. Eu esperava que houvesse uma solução de uso mais geral.
— brien

2
Não que eu saiba - eu suspeito que padrões complexos acabem sendo mais específicos do banco de dados e difíceis de expressar de uma maneira geral.
— Jon Skeet

4
@Jon Skeet: até onde sei, a funcionalidade LIKE está no padrão ANSI e é praticamente a mesma no SQL Server, Oracle e DB2.
— AK

2
Uma coisa que eu vi com o uso desses operadores e MS SQL é que EF os adiciona como parâmetros de escape "Name LIKE @ p__linq__1 ESCAPE N '' ~ ''" que em meu caso de uso muito limitado tem desempenho muito mais lento do que se a string de pesquisa está apenas na consulta "Nome como '% xyz%'. Para os cenários que tenho, ainda estou usando StartsWith e Contains, mas faço isso via linq dinâmico porque isso injeta o parâmetro na instrução SQL que em meu cenário está produzindo um consulta mais eficiente. Não tenho certeza se isso é uma coisa EF 4.0 ou não. Você também pode usar ObjectQueryParameters para conseguir a mesma coisa ...
— Shane Neuville

34

Eu tive o mesmo problema.

Por enquanto, resolvi usar a filtragem Wildcard / Regex do lado do cliente com base em http://www.codeproject.com/Articles/11556/Converting-Wildcards-to-Regexes?msg=1423024#xx1423024xx - é simples e funciona como esperado.

Eu encontrei outra discussão sobre este tópico: http://forums.asp.net/t/1654093.aspx/2/10
Esta postagem parece promissora se você usar o Entity Framework> = 4.0:

Use SqlFunctions.PatIndex:

http://msdn.microsoft.com/en-us/library/system.data.objects.sqlclient.sqlfunctions.patindex.aspx

Como isso:

var q = EFContext.Products.Where(x =>
SqlFunctions.PatIndex("%CD%BLUE%", x.ProductName) > 0);

Nota: esta solução é apenas para SQL-Server, porque usa a função PATINDEX não padrão.


Embora PatIndex "funcione", ele voltará para mordê-lo, PatIndex na cláusula where não usa os índices na coluna que você gostaria de filtrar.
— BlackICE de

@BlackICE isso é esperado. Quando você pesquisa em texto interno (% CD% BLUE%), o servidor não poderá usar índices. Sempre que possível, pesquisar texto desde o início (CD% BLUE%) é mais eficiente.
— surfen

@surfen patindex é pior do que isso, porém, ele não usará o índice mesmo sem% na frente, pesquisar por (BLUE CD%) com patindex não usará o índice de coluna.
— BlackICE

22

Atualização: No EF 6.2 existe um operador semelhante

Where(obj => DbFunctions.Like(obj.Column , "%expression%")

Este não seria um exemplo mais claro Where(obj => DbFunctions.Like(obj.Column , "%expression%"):?
— DCD

Com certeza é. Alterado
— Lode Vlaeminck

19

Existe um LIKEoperador adicionado em Entity Framework Core 2.0:

var query = from e in _context.Employees
                    where EF.Functions.Like(e.Title, "%developer%")
                    select e;

Comparando com ... where e.Title.Contains("developer") ...ele é realmente traduzido em SQL LIKEvez de CHARINDEXvermos como Containsmétodo.


5

É especificamente mencionado na documentação como parte do Entity SQL. Você está recebendo uma mensagem de erro?

// LIKE and ESCAPE
// If an AdventureWorksEntities.Product contained a Name 
// with the value 'Down_Tube', the following query would find that 
// value.
Select value P.Name FROM AdventureWorksEntities.Product 
    as P where P.Name LIKE 'DownA_%' ESCAPE 'A'

// LIKE
Select value P.Name FROM AdventureWorksEntities.Product 
    as P where P.Name like 'BB%'

http://msdn.microsoft.com/en-us/library/bb399359.aspx


1
Eu ficaria tentado a ficar longe do Entity SQL caso você queira se afastar da EF no futuro. Jogue pelo seguro e use as opções Contains (), StartsWith () e EndsWith () na resposta original.
— Stephen Newman

1
Isso compila bem, mas falha em tempo de execução.
— brien

O código que postei falha em tempo de execução? Ele vem do link da Microsoft.
— Robert Harvey

Editei a pergunta com um link para uma postagem de blog que descreve o mesmo problema que estamos tendo.
— brien

Parece que Contains () é o seu tíquete. Mas, como Jon Skeet apontou, você pode ter que descer para algum SQL real manipulando o banco de dados diretamente, se o Contains não atender às suas necessidades.
— Robert Harvey

2

se você estiver usando o MS Sql, escrevi 2 métodos de extensão para oferecer suporte ao caractere% para a pesquisa curinga. (LinqKit é necessário)

public static class ExpressionExtension
{
    public static Expression<Func<T, bool>> Like<T>(Expression<Func<T, string>> expr, string likeValue)
    {
        var paramExpr = expr.Parameters.First();
        var memExpr = expr.Body;

        if (likeValue == null || likeValue.Contains('%') != true)
        {
            Expression<Func<string>> valExpr = () => likeValue;
            var eqExpr = Expression.Equal(memExpr, valExpr.Body);
            return Expression.Lambda<Func<T, bool>>(eqExpr, paramExpr);
        }

        if (likeValue.Replace("%", string.Empty).Length == 0)
        {
            return PredicateBuilder.True<T>();
        }

        likeValue = Regex.Replace(likeValue, "%+", "%");

        if (likeValue.Length > 2 && likeValue.Substring(1, likeValue.Length - 2).Contains('%'))
        {
            likeValue = likeValue.Replace("[", "[[]").Replace("_", "[_]");
            Expression<Func<string>> valExpr = () => likeValue;
            var patExpr = Expression.Call(typeof(SqlFunctions).GetMethod("PatIndex",
                new[] { typeof(string), typeof(string) }), valExpr.Body, memExpr);
            var neExpr = Expression.NotEqual(patExpr, Expression.Convert(Expression.Constant(0), typeof(int?)));
            return Expression.Lambda<Func<T, bool>>(neExpr, paramExpr);
        }

        if (likeValue.StartsWith("%"))
        {
            if (likeValue.EndsWith("%") == true)
            {
                likeValue = likeValue.Substring(1, likeValue.Length - 2);
                Expression<Func<string>> valExpr = () => likeValue;
                var containsExpr = Expression.Call(memExpr, typeof(String).GetMethod("Contains",
                    new[] { typeof(string) }), valExpr.Body);
                return Expression.Lambda<Func<T, bool>>(containsExpr, paramExpr);
            }
            else
            {
                likeValue = likeValue.Substring(1);
                Expression<Func<string>> valExpr = () => likeValue;
                var endsExpr = Expression.Call(memExpr, typeof(String).GetMethod("EndsWith",
                    new[] { typeof(string) }), valExpr.Body);
                return Expression.Lambda<Func<T, bool>>(endsExpr, paramExpr);
            }
        }
        else
        {
            likeValue = likeValue.Remove(likeValue.Length - 1);
            Expression<Func<string>> valExpr = () => likeValue;
            var startsExpr = Expression.Call(memExpr, typeof(String).GetMethod("StartsWith",
                new[] { typeof(string) }), valExpr.Body);
            return Expression.Lambda<Func<T, bool>>(startsExpr, paramExpr);
        }
    }

    public static Expression<Func<T, bool>> AndLike<T>(this Expression<Func<T, bool>> predicate, Expression<Func<T, string>> expr, string likeValue)
    {
        var andPredicate = Like(expr, likeValue);
        if (andPredicate != null)
        {
            predicate = predicate.And(andPredicate.Expand());
        }
        return predicate;
    }

    public static Expression<Func<T, bool>> OrLike<T>(this Expression<Func<T, bool>> predicate, Expression<Func<T, string>> expr, string likeValue)
    {
        var orPredicate = Like(expr, likeValue);
        if (orPredicate != null)
        {
            predicate = predicate.Or(orPredicate.Expand());
        }
        return predicate;
    }
}

uso

var orPredicate = PredicateBuilder.False<People>();
orPredicate = orPredicate.OrLike(per => per.Name, "He%llo%");
orPredicate = orPredicate.OrLike(per => per.Name, "%Hi%");

var predicate = PredicateBuilder.True<People>();
predicate = predicate.And(orPredicate.Expand());
predicate = predicate.AndLike(per => per.Status, "%Active");

var list = dbContext.Set<People>().Where(predicate.Expand()).ToList();    

em ef6 e deve ser traduzido para

....
from People per
where (
    patindex(@p__linq__0, per.Name) <> 0
    or per.Name like @p__linq__1 escape '~'
) and per.Status like @p__linq__2 escape '~'

', @ p__linq__0 ='% He% llo% ', @ p__linq__1 ='% Hi% ', @ p__linq_2 ='% ativo '


Obrigado pelo seu comentário Ronel, há algo que eu possa ajudar? qual é a mensagem de erro?
— Steven Chong

2

Para EfCore, aqui está um exemplo para construir a expressão LIKE

protected override Expression<Func<YourEntiry, bool>> BuildLikeExpression(string searchText)
    {
        var likeSearch = $"%{searchText}%";

        return t => EF.Functions.Like(t.Code, likeSearch)
                    || EF.Functions.Like(t.FirstName, likeSearch)
                    || EF.Functions.Like(t.LastName, likeSearch);
    }

//Calling method

var query = dbContext.Set<YourEntity>().Where(BuildLikeExpression("Text"));

0

Você pode usar um real como em Link para Entidades com bastante facilidade

Adicionar

    <Function Name="String_Like" ReturnType="Edm.Boolean">
      <Parameter Name="searchingIn" Type="Edm.String" />
      <Parameter Name="lookingFor" Type="Edm.String" />
      <DefiningExpression>
        searchingIn LIKE lookingFor
      </DefiningExpression>
    </Function>

ao seu EDMX nesta tag:

edmx: Edmx / edmx: Runtime / edmx: ConceptualModels / Schema

Lembre-se também do namespace no <schema namespace="" />atributo

Em seguida, adicione uma classe de extensão no namespace acima:

public static class Extensions
{
    [EdmFunction("DocTrails3.Net.Database.Models", "String_Like")]
    public static Boolean Like(this String searchingIn, String lookingFor)
    {
        throw new Exception("Not implemented");
    }
}

Este método de extensão agora será mapeado para a função EDMX.

Mais informações aqui: http://jendaperl.blogspot.be/2011/02/like-in-linq-to-entities.html

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.