¿Como operador en Entity Framework?

93

Estamos tratando de implementar el operador "LIKE" en Entity Framework para nuestras entidades con campos de cadena, pero no parece ser compatible. ¿Alguien más ha intentado hacer algo como esto?

Esta publicación de blog resume el problema que tenemos. Podríamos usar contiene, pero eso solo coincide con el caso más trivial de LIKE. La combinación de contains, startswith, endswith e indexof nos lleva allí, pero requiere una traducción entre comodines estándar y código Linq to Entities.

brien
fuente
1
Vaya a esta respuesta si ya está usando EF 6.2.x. A esta respuesta si está usando EF Core 2.x
CodeNotFound

Respuestas:

36

Esta es una publicación antigua ahora, pero para cualquiera que busque la respuesta, este enlace debería ayudar. Vaya a esta respuesta si ya está usando EF 6.2.x. A esta respuesta si está usando EF Core 2.x

Version corta:

Método SqlFunctions.PatIndex : devuelve la posición inicial de la primera aparición de un patrón en una expresión especificada, o ceros si no se encuentra el patrón, en todos los tipos de datos de texto y caracteres válidos

Espacio de nombres: System.Data.Objects.SqlClient Ensamblado: System.Data.Entity (en System.Data.Entity.dll)

También aparece una pequeña explicación en este hilo del foro .

Yann Duran
fuente
59
¿Cómo es la respuesta aceptada la que enlaza con un foro de MSDN que enlaza con esta pregunta a la respuesta a continuación ?
Eonasdan
La respuesta fue utilizar el método SqlFunctions.PatIndex. El hilo del foro vinculado fue para proporcionar un poco más de información "de fondo".
Yann Duran
La respuesta a continuación es buena para patrones simples, pero si quiero decir "DONDE Nombre COMO 'abc [0-9]%'" o algún otro patrón más complejo, simplemente usar Contains () no es suficiente.
HotN
1
Duplica de esta respuesta anterior a esta pregunta. (No de su primera parte, sino de su solución alternativa.)
Frédéric
154

Realmente no sé nada sobre EF, pero en LINQ to SQL generalmente expresas una cláusula LIKE usando String.Contains:

where entity.Name.Contains("xyz")

se traduce en

WHERE Name LIKE '%xyz%'

(Uso StartsWithy EndsWithpara otro comportamiento).

No estoy completamente seguro de si eso es útil, porque no entiendo lo que quiere decir cuando dice que está tratando de implementar LIKE. Si he entendido mal por completo, avíseme y eliminaré esta respuesta :)

Jon Skeet
fuente
4
tenga en cuenta que "WHERE Name LIKE '% xyz%'" no podrá usar un índice, por lo que si la tabla es enorme, es posible que no funcione tan bien ...
Mitch Wheat
1
Bueno, nos gustaría poder hacer coincidir en bla * blah foo bar foo? Bar? Foo bar? y otros patrones complejos. Nuestro enfoque actual es similar al que mencionaste, convertiríamos esas consultas en operaciones usando contiene, indexof, startswith, endswith, etc. Solo esperaba que hubiera una solución de propósito más general.
brien
2
No que yo sepa, sospecho que los patrones complejos terminan siendo más específicos de la base de datos y difíciles de expresar de manera general.
Jon Skeet
4
@Jon Skeet: que yo sepa, la funcionalidad LIKE está en el estándar ANSI y es prácticamente la misma en SQL Server, Oracle y DB2.
AK
2
Una cosa que he visto al usar estos operadores y MS SQL es que EF los agrega como parámetros de escape "Nombre LIKE @ p__linq__1 ESCAPE N '' ~ ''" que en mi caso de uso muy limitado funciona mucho más lento que si la cadena de búsqueda está en la consulta "Nombre como '% xyz%'. Para los escenarios que tengo, sigo usando StartsWith y Contiene, pero lo hago a través de Dynamic Linq porque inyecta el parámetro en la declaración SQL que en mi escenario está produciendo un consulta más eficiente. No estoy seguro de si esto es algo de EF 4.0 o no. También puede usar ObjectQueryParameters para lograr lo mismo ...
Shane Neuville
35

Yo tuve el mismo problema.

Por ahora, me he conformado con el filtrado Wildcard / Regex del lado del cliente basado en http://www.codeproject.com/Articles/11556/Converting-Wildcards-to-Regexes?msg=1423024#xx1423024xx - es simple y funciona como esperado.

Encontré otra discusión sobre este tema: http://forums.asp.net/t/1654093.aspx/2/10
Esta publicación parece prometedora si usa Entity Framework> = 4.0:

Utilice SqlFunctions.PatIndex:

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

Me gusta esto:

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

Nota: esta solución es solo para SQL-Server, ya que utiliza una función PATINDEX no estándar.

surfear
fuente
Mientras PatIndex "funciona", volverá a morderlo, PatIndex en la cláusula where no usa los índices de la columna en la que le gustaría filtrar.
BlackICE
@BlackICE esto es lo esperado. Cuando busca en texto interno (% CD% BLUE%), el servidor no podrá usar índices. Siempre que sea posible, la búsqueda de texto desde el principio (CD% BLUE%) es más eficiente.
surfen
@surfen patindex es peor que eso, sin embargo, no usará el índice incluso sin% al frente, la búsqueda de (BLUE CD%) con patindex no usará el índice de columna.
BlackICE
23

Actualización: en EF 6.2 hay un operador similar

Where(obj => DbFunctions.Like(obj.Column , "%expression%")
Lode Vlaeminck
fuente
¿No sería este un ejemplo más claro Where(obj => DbFunctions.Like(obj.Column , "%expression%")?
DCD
Seguro que lo es. Lo cambió
Lode Vlaeminck
20

Hay un LIKEoperador agregado en Entity Framework Core 2.0:

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

Compararlo ... where e.Title.Contains("developer") ...se traduce en realidad en SQL LIKElugar de CHARINDEXverlo como Containsmétodo.

Dmitry Pavlov
fuente
5

Se menciona específicamente en la documentación como parte de Entity SQL. ¿Está recibiendo un mensaje de error?

// 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
fuente
1
Me sentiría tentado a alejarme de Entity SQL en caso de que quisiera alejarse de EF en el futuro. Vaya a lo seguro y quédese con las opciones Contains (), StartsWith () y EndsWith () en la respuesta original.
Stephen Newman
1
Eso se compila bien, pero falla en tiempo de ejecución.
brien
¿El código que publiqué falla en tiempo de ejecución? Viene del enlace de Microsoft.
Robert Harvey
Edité la pregunta con un enlace a una publicación de blog que describe el mismo problema que estamos teniendo.
brien
Parece que Contains () es su boleto. Pero como señaló Jon Skeet, es posible que tenga que pasar a algún SQL real que manipule la base de datos directamente, si Contains no satisface sus necesidades.
Robert Harvey
2

si está utilizando MS Sql, he escrito 2 métodos de extensión para admitir el carácter% para la búsqueda con comodines. (Se requiere LinqKit)

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

en ef6 y debería traducirse a

....
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 ='% Hola% ', @ p__linq_2 ='% Activo '

Steven Chong
fuente
gracias por tu comentario Ronel, ¿puedo ayudarte en algo? ¿cuál es el mensaje de error?
Steven Chong
2

Para EfCore, aquí hay una muestra para construir una expresión 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
fuente
0

Puede usar un Me gusta real en Enlace a entidades con bastante facilidad

Añadir

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

a su EDMX en esta etiqueta:

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

También recuerde el espacio de nombres en el <schema namespace="" />atributo

Luego agregue una clase de extensión en el espacio de nombres anterior:

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 extensión ahora se asignará a la función EDMX.

Más información aquí: http://jendaperl.blogspot.be/2011/02/like-in-linq-to-entities.html

brechtvhb
fuente