String.IsNullOrWhiteSpace en LINQ Expression

151

Tengo el siguiente código:

return this.ObjectContext.BranchCostDetails.Where(
    b => b.TarrifId == tariffId && b.Diameter == diameter
        || (b.TarrifId==tariffId && !string.IsNullOrWhiteSpace(b.Diameter))
        || (!b.TarrifId.HasValue) && b.Diameter==diameter);

Y obtengo este error cuando intento ejecutar el código:

LINQ to Entities no reconoce el método 'Boolean IsNullOrWhiteSpace (System.String)', y este método no se puede traducir a una expresión de tienda ".

¿Cómo puedo resolver este problema y escribir código mejor que esto?

Hossein Moradinia
fuente

Respuestas:

263

Necesitas reemplazar

!string.IsNullOrWhiteSpace(b.Diameter)

con

!(b.Diameter == null || b.Diameter.Trim() == string.Empty)

Para Linq to Entities esto se traduce en:

DECLARE @p0 VarChar(1000) = ''
...
WHERE NOT (([t0].[Diameter] IS NULL) OR (LTRIM(RTRIM([t0].[Diameter])) = @p0))

y para Linq to SQL casi pero no exactamente lo mismo

DECLARE @p0 NVarChar(1000) = ''
...
WHERE NOT (LTRIM(RTRIM([t0].[TypeName])) = @p0)
Phil
fuente
3
¿Por qué? Este código compila:List<string> my = new List<string>(); var i = from m in my where !string.IsNullOrWhiteSpace(m) select m;
Eric J.
37
Puede compilarse, pero Linq no lo traducirá a SQL a entidades. El método 'Boolean IsNullOrWhiteSpace (System.String)' no tiene traducción compatible a SQL. Lo mismo se aplica para IsNullOrEmpty.
Phil
1
Lo mismo es cierto para Linq to SQL
Phil
3
Una advertencia: es de suma importancia emplear 'string.Empty' sobre "" (también conocido como string vacío). El primero funciona, el segundo no (al menos en lo que respecta al controlador EF de Oracle). Aka si usa: b.Diameter.Trim () == "" <- esto no funcionará como se esperaba (loco, lo sé ...)
XDS
parece que Trim () tampoco es compatible al menos para consultas con MongoDB.Driver
Leandro hereñu
20

En este caso es importante distinguir entre IQueryable<T>y IEnumerable<T>. En resumen, IQueryable<T>es procesado por un proveedor de LINQ para entregar una consulta optimizada. Durante esta transformación, no se admiten todas las declaraciones de C #, ya que no es posible traducirlas a una consulta específica de fondo (por ejemplo, SQL) o porque el implementador no previó la necesidad de la declaración.

Por el contrario, IEnumerable<T>se ejecuta contra los objetos concretos y, por lo tanto, no se transformará. Por lo tanto, es bastante común que las construcciones, que son utilizables con IEnumerable<T>, no se puedan usar IQueryable<T>y también que IQueryables<T>respaldadas por diferentes proveedores de LINQ no admitan el mismo conjunto de funciones.

Sin embargo, hay algunas soluciones (como la respuesta de Phil ), que modifican la consulta. Además, como un enfoque más general, es posible volver a IEnumerable<T>antes de continuar con la especificación de la consulta. Esto, sin embargo, podría tener un impacto en el rendimiento, especialmente cuando se usa en restricciones (por ejemplo, cláusulas where). Por el contrario, cuando se trata de transformaciones, el impacto en el rendimiento es mucho menor, a veces incluso inexistente, dependiendo de su consulta.

Entonces, el código anterior también podría reescribirse así:

return this.ObjectContext.BranchCostDetails
    .AsEnumerable()
    .Where(
        b => b.TarrifId == tariffId && b.Diameter == diameter
        || (b.TarrifId==tariffId && !string.IsNullOrWhiteSpace(b.Diameter))
        ||(!b.TarrifId.HasValue) && b.Diameter==diameter
    );

NOTA: Este código tendrá un mayor impacto en el rendimiento que la respuesta de Phil . Sin embargo, muestra el principio.

AxelEckenberger
fuente
10

Use un visitante de expresión para detectar referencias a string.IsNullOrWhiteSpace y desglosarlas en una expresión más simple (x == null || x.Trim() == string.Empty).

A continuación, se muestra un visitante extendido y un método de extensión para utilizarlo. No requiere una configuración especial para usar, simplemente llame a WhereEx en lugar de Where.

public class QueryVisitor: ExpressionVisitor
{
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Method.IsStatic && node.Method.Name == "IsNullOrWhiteSpace" && node.Method.DeclaringType.IsAssignableFrom(typeof(string)))
        {
            //!(b.Diameter == null || b.Diameter.Trim() == string.Empty)
            var arg = node.Arguments[0];
            var argTrim = Expression.Call(arg, typeof (string).GetMethod("Trim", Type.EmptyTypes));

            var exp = Expression.MakeBinary(ExpressionType.Or,
                    Expression.MakeBinary(ExpressionType.Equal, arg, Expression.Constant(null, arg.Type)),
                    Expression.MakeBinary(ExpressionType.Equal, argTrim, Expression.Constant(string.Empty, arg.Type))
                );

            return exp;
        }

        return base.VisitMethodCall(node);
    }
}

public static class EfQueryableExtensions
{
    public static IQueryable<T> WhereEx<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> where)
    {
        var visitor = new QueryVisitor();
        return queryable.Where(visitor.VisitAndConvert(where, "WhereEx"));
    }
}

Entonces, si ejecuta myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace()), se convertirá a !(c.Name == null || x.Trim() == "")antes de pasar a lo que sea (linq a sql / entidades) y se convertirá a sql.

Sam
fuente
Mucho más complejo que la respuesta de Phil para un requisito tan simple, pero muy interesante para fines educativos con respecto a ExpressionVisitor, gracias
AFract
2

También puede usar esto para verificar los espacios en blanco:

b.Diameter!=null && !String.IsNullOrEmpty(b.Diameter.Trim())
Majid
fuente
66
Esto arrojará una excepción si el diámetro es nulo.
Okan Kocyigit el
@OkanKocyigit Tienes razón. He editado la respuesta. :)
Majid
0
!String.IsNullOrEmpty(b.Diameter.Trim()) 

lanzará una excepción si b.Diameteres así null.
Si aún desea usar su estado de cuenta, mejor use este cheque

!String.IsNullOrWhiteSpace(b.Diameter), IsNullOrWhiteSpace = IsNullOrEmpty + WhiteSpace
Duy Tran
fuente
2
¡Bienvenido a StackOverflow! En primer lugar, gracias por participar en SO como respondedor. Eche un vistazo al formato para crear una respuesta clara y fácil de leer.
Hille