2012-03-07 13 views
107

Tengo el siguiente código:String.IsNullOrWhiteSpace en LINQ Expresión

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 me sale este error cuando trato de ejecutar el código:

LINQ a Entidades no reconoce el método ' boolean IsNullOrWhiteSpace método (System.String)', y este método no puede ser traduce en una expresión tienda."

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

Respuesta

209

Es necesario sustituir

!string.IsNullOrWhiteSpace(b.Diameter) 

con

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

Para LINQ a Entidades 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) 
+3

¿Por qué? Este código compila: 'List my = new List (); var i = from m en my where! string.IsNullOrWhiteSpace (m) select m; ' –

+29

Puede compilarse, pero Linq no lo traduce a SQL en entidades. * El método 'Boolean IsNullOrWhiteSpace (System.String)' no tiene traducción soportada a SQL. * Lo mismo aplica para IsNullOrEmpty. – Phil

+0

Tiene sentido ... –

13

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 instrucciones C#, ya que no es posible traducirlas a una consulta específica de back-end (por ejemplo, SQL) o porque el implementador no previó la necesidad de la declaración.

En el contrato 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 con IQueryable<T> y también que IQueryables<T> respaldado por diferentes proveedores de LINQ no admitan el mismo conjunto de funciones.

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

Así que el código anterior también podría ser reescrita como esto:

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: código Ths tendrá un impacto en el rendimiento más alto que Phil's answer. Sin embargo, muestra el principio.

4

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

A continuación, se incluye un visitante ampliado y un método de extensión para utilizarlo. No requiere 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")); 
    } 
} 

Así que si ejecuta myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace()) que se convertirá en !(c.Name == null || x.Trim() == "") antes de ser pasadas a lo que sea (LINQ to SQL/entidades) y se convierte a SQL.

+0

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

1

también puede utilizar este para comprobar si hay espacios en blanco:

!(String.IsNullOrEmpty(b.Diameter.Trim()); 
+0

esto arrojará una excepción si el diámetro es nulo . – ocanal

Cuestiones relacionadas