String.IsNullOrWhiteSpace im LINQ-Ausdruck

151

Ich habe folgenden Code:

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

Und ich bekomme diesen Fehler, wenn ich versuche, den Code auszuführen:

LINQ to Entities erkennt die Methode 'Boolean IsNullOrWhiteSpace (System.String)' nicht und diese Methode kann nicht in einen Speicherausdruck übersetzt werden. "

Wie kann ich dieses Problem lösen und Code besser schreiben?

Hossein Moradinia
quelle

Antworten:

263

Sie müssen ersetzen

!string.IsNullOrWhiteSpace(b.Diameter)

mit

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

Für Linq to Entities wird dies übersetzt in:

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

und für Linq zu SQL fast aber nicht ganz das gleiche

DECLARE @p0 NVarChar(1000) = ''
...
WHERE NOT (LTRIM(RTRIM([t0].[TypeName])) = @p0)
Phil
quelle
3
Warum? Dieser Code kompiliert:List<string> my = new List<string>(); var i = from m in my where !string.IsNullOrWhiteSpace(m) select m;
Eric J.
37
Es kann kompiliert werden, wird jedoch von Linq nicht in Entitäten in SQL übersetzt. Die Methode 'Boolean IsNullOrWhiteSpace (System.String)' unterstützt keine Übersetzung nach SQL. Gleiches gilt für IsNullOrEmpty.
Phil
1
Gleiches gilt für Linq to SQL
Phil
3
Ein Wort der Vorsicht: Es ist von größter Bedeutung, 'string.Empty' über "" (auch bekannt als leere Zeichenfolge) zu verwenden. Ersteres funktioniert, letzteres nicht (zumindest was den EF-Treiber von Oracle betrifft). Aka, wenn Sie verwenden: b.Diameter.Trim () == "" <- dies funktioniert nicht wie beabsichtigt (verrückt, ich weiß ...)
XDS
Anscheinend wird Trim () auch nicht unterstützt, zumindest nicht für Abfragen mit MongoDB.Driver
Leandro hereñu
20

In diesem Fall ist es wichtig, zwischen IQueryable<T>und zu unterscheiden IEnumerable<T>. ZusamenfassendIQueryable<T> wird von einem LINQ-Anbieter verarbeitet, um eine optimierte Abfrage zu liefern. Während dieser Umwandlung werden nicht alle C # -Anweisungen unterstützt, da sie entweder nicht in eine Back-End-spezifische Abfrage (z. B. SQL) übersetzt werden können oder weil der Implementierer die Notwendigkeit der Anweisung nicht vorausgesehen hat.

Im Gegensatz dazu IEnumerable<T>wird gegen die konkreten Objekte ausgeführt und wird daher nicht transformiert. Daher ist es durchaus üblich, dass Konstrukte, mit denen verwendet werden IEnumerable<T>kann, nicht verwendet werden können IQueryable<T>und IQueryables<T>die von verschiedenen LINQ-Anbietern unterstützt werden, nicht dieselben Funktionen unterstützen.

Es gibt jedoch einige Problemumgehungen (wie Phils Antwort ), die die Abfrage ändern. Als allgemeinerer Ansatz ist es auch möglich, auf ein zurückzugreifenIEnumerable<T> bevor Sie mit der Spezifikation der Abfrage fortfahren. Dies kann jedoch zu Leistungseinbußen führen - insbesondere bei Verwendung für Einschränkungen (z. B. where-Klauseln). Im Gegensatz dazu ist der Leistungseinbruch bei Transformationen viel geringer, manchmal sogar nicht vorhanden - abhängig von Ihrer Abfrage.

Der obige Code könnte also auch folgendermaßen umgeschrieben werden:

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

HINWEIS: Dieser Code hat eine höhere Auswirkung auf die Leistung als Phils Antwort . Es zeigt jedoch das Prinzip.

AxelEckenberger
quelle
10

Verwenden Sie einen Ausdrucksbesucher, um Verweise auf string.IsNullOrWhiteSpace zu erkennen und in einen einfacheren Ausdruck zu zerlegen (x == null || x.Trim() == string.Empty).

Im Folgenden finden Sie einen erweiterten Besucher und eine Erweiterungsmethode, um davon Gebrauch zu machen. Für die Verwendung ist keine spezielle Konfiguration erforderlich. Rufen Sie einfach WhereEx anstelle von Where auf.

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

Wenn Sie es also ausführen myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace()), wird es in konvertiert, !(c.Name == null || x.Trim() == "")bevor es an was auch immer übergeben wird (linq in sql / entity) und in sql konvertiert.

Sam
quelle
Viel komplexer als Phils Antwort auf eine so einfache Anforderung, aber für Bildungszwecke in Bezug auf ExpressionVisitor sehr interessant, danke
AFract
2

Sie können dies auch verwenden, um nach Leerzeichen zu suchen:

b.Diameter!=null && !String.IsNullOrEmpty(b.Diameter.Trim())
Majid
quelle
6
Dies löst eine Ausnahme aus, wenn der Durchmesser null ist.
Okan Kocyigit
@OkanKocyigit Du hast recht. Ich habe die Antwort bearbeitet. :)
Majid
0
!String.IsNullOrEmpty(b.Diameter.Trim()) 

wird eine Ausnahme auslösen, wenn dies der Fall b.Diameterist null.
Wenn Sie Ihre Anweisung dennoch verwenden möchten, verwenden Sie diese Prüfung besser

!String.IsNullOrWhiteSpace(b.Diameter), IsNullOrWhiteSpace = IsNullOrEmpty + WhiteSpace
Duy Tran
quelle
2
Willkommen bei StackOverflow! Zunächst einmal vielen Dank, dass Sie als Antwortender an SO teilgenommen haben. Bitte schauen Sie sich die Formatierung an, um eine klare und leicht lesbare Antwort zu erhalten.
Hille