2012-03-07 22 views
107

Ho il seguente codice:String.IsNullOrWhiteSpace in LINQ Expression

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

E ottengo questo errore quando provo a eseguire il codice:

LINQ to Entities non riconosce il metodo ' boolean IsNullOrWhiteSpace (System.String)' metodo, e questo metodo non può essere tradotto in un'espressione negozio."

Come posso risolvere questo problema e scrivere codice migliore di questo?

risposta

209

è necessario sostituire

!string.IsNullOrWhiteSpace(b.Diameter) 

con

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

Per LINQ to Entities questo si traduce in:

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

e per LINQ to SQL quasi, ma non proprio la stessa cosa

DECLARE @p0 NVarChar(1000) = '' 
... 
WHERE NOT (LTRIM(RTRIM([t0].[TypeName])) = @p0) 
+3

Perché? Questo codice compila: 'Lista my = new List (); var i = da m nel mio dove! string.IsNullOrWhiteSpace (m) seleziona m; ' –

+29

Può essere compilato, ma non verrà tradotto in SQL da Linq alle entità. * Il metodo 'Boolean IsNullOrWhiteSpace (System.String)' non ha traduzione supportata in SQL. * Lo stesso vale per IsNullOrEmpty. – Phil

+0

Ha senso ... –

13

In questo caso è importante distinguere tra IQueryable<T> e IEnumerable<T>. In breve ist. IQueryable<T> elaborato da un provider LINQ per fornire una query ottimizzata. Durante questa trasformazione non sono supportate tutte le istruzioni C#, in quanto non è possibile tradurle in una query specifica di back-end (ad esempio SQL) o perché l'implementatore non ha previsto la necessità dell'istruzione.

Nel contratto IEnumerable<T> viene eseguito contro gli oggetti concreti e, pertanto, non verrà trasformato. Pertanto, è abbastanza comune che i costrutti, che sono utilizzabili con IEnumerable<T>, non possano essere utilizzati con IQueryable<T> e anche che il IQueryables<T> supportato da diversi provider LINQ non supportino lo stesso insieme di funzioni.

Tuttavia, vi sono alcune soluzioni alternative (come Phil's answer), che modificano la query. Inoltre, come approccio più generale è possibile tornare a un IEnumerable<T> prima di continuare con le specifiche della query. Ciò, tuttavia, potrebbe avere un impatto sulle prestazioni, specialmente quando viene utilizzato su restrizioni (ad esempio, clausole). Al contrario, quando si ha a che fare con le trasformazioni, l'impatto sulle prestazioni è molto più piccolo, a volte addirittura inesistente, a seconda della query.

Così il codice di cui sopra potrebbe anche essere riscritto così:

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: codice Ths avrà un impatto sulle prestazioni superiori Phil's answer. Tuttavia, mostra il principio.

4

Utilizzare un visitatore di espressioni per rilevare i riferimenti a string.IsNullOrWhiteSpace e suddividerli in un'espressione più semplice (x == null || x.Trim() == string.Empty).

Quindi di seguito è un visitatore esteso e un metodo di estensione per farne uso. Non richiede alcuna configurazione speciale da utilizzare, basta chiamare WhereEx anziché 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")); 
    } 
} 

Quindi, se si esegue myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace()) che verrà convertito in !(c.Name == null || x.Trim() == "") prima di essere passate per tutto ciò (LINQ to SQL/entità) e convertito in SQL.

+0

Molto più complesso della risposta di Phil per un requisito così semplice, ma molto interessante per scopi didattici per quanto riguarda ExpressionVisitor, grazie a – AFract

1

È inoltre possibile utilizzare questo per verificare la presenza di spazi bianchi:

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

questo genererà un'eccezione se il diametro è nullo . – ocanal