String.IsNullOrWhiteSpace in LINQ Expression


151

Ho il codice seguente:

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 ad eseguire il codice:

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

Come posso risolvere questo problema e scrivere codice meglio di così?

Risposte:


263

È necessario sostituire

!string.IsNullOrWhiteSpace(b.Diameter)

con

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

Per Linq to Entities questo viene tradotto in:

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

e per Linq a SQL quasi ma non è lo stesso

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

3
Perché? Questo codice compila:List<string> my = new List<string>(); var i = from m in my where !string.IsNullOrWhiteSpace(m) select m;
Eric J.

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

1
Lo stesso vale per Linq to SQL
Phil

3
Un avvertimento: è di fondamentale importanza impiegare 'string.Empty' over "" (ovvero la stringa vuota). Il primo funziona il secondo no (almeno per quanto riguarda il driver EF di Oracle). Anche se usi: b.Diameter.Trim () == "" <- questo non funzionerà come previsto (pazzo lo so ...)
XDS

sembra che Trim () non sia supportato almeno per le query che utilizzano MongoDB.Driver
Leandro qui

20

In questo caso è importante distinguere tra IQueryable<T>e IEnumerable<T>. In breve, IQueryable<T>viene 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 back-end (ad esempio SQL) o perché l'implementatore non ha previsto la necessità dell'istruzione.

Al contrario, IEnumerable<T>viene eseguito contro gli oggetti concreti e, pertanto, non verrà trasformato. Quindi, è abbastanza comune che i costrutti, che sono utilizzabili IEnumerable<T>, non possano essere utilizzati con IQueryable<T>e anche che IQueryables<T>supportati da diversi provider LINQ non supportano lo stesso insieme di funzioni.

Tuttavia, esistono alcune soluzioni alternative (come la risposta di Phil ) che modificano la query. Inoltre, come approccio più generale è possibile tornare a un IEnumerable<T>prima di continuare con la specifica della query. Questo, tuttavia, potrebbe avere un impatto sulle prestazioni, specialmente quando lo si utilizza su restrizioni (ad es. Dove clausole). Al contrario, quando si ha a che fare con le trasformazioni, l'hit di prestazioni è molto più piccolo, a volte anche inesistente, a seconda della query.

Quindi il codice sopra potrebbe anche essere riscritto in questo modo:

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: questo codice avrà un impatto sulle prestazioni superiore rispetto alla risposta di Phil . Tuttavia, mostra il principio.


10

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

Di seguito è riportato un visitatore esteso e un metodo di estensione per utilizzarlo. Non richiede alcuna configurazione speciale da usare, basta chiamare WhereEx invece di 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 lo esegui myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace()), verrà convertito in !(c.Name == null || x.Trim() == "")prima di essere passato a qualunque cosa (linq in sql / entity) e convertito in sql.


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

2

Puoi anche usarlo per controllare gli spazi bianchi:

b.Diameter!=null && !String.IsNullOrEmpty(b.Diameter.Trim())

6
questo genererà un'eccezione se il diametro è nullo.
Okan Kocyigit,

@OkanKocyigit Hai ragione. Ho modificato la risposta. :)
Majid

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

genererà un'eccezione se lo b.Diameterè null.
Se desideri comunque utilizzare la tua dichiarazione, è meglio utilizzare questo controllo

!String.IsNullOrWhiteSpace(b.Diameter), IsNullOrWhiteSpace = IsNullOrEmpty + WhiteSpace

2
Benvenuto in StackOverflow! Prima di tutto, grazie per aver partecipato a SO come risponditore. Dai un'occhiata alla formattazione per creare una risposta chiara e di facile lettura.
Hille,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.