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.

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

Yann Duran
fonte
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 :)

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

surfar
fonte
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%")
Lode Vlaeminck
fonte
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.

Dmitry Pavlov
fonte
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

Robert Harvey
fonte
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 '

Steven Chong
fonte
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"));
Duy Hoang
fonte
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

Brechtvhb
fonte