wake-up-neo.com

String.IsNullOrWhiteSpace im LINQ-Ausdruck

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 diese Fehlermeldung, 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 Geschäftsausdruck übersetzt werden. "

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

136

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 to SQL fast, aber nicht ganz dasselbe

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

In diesem Fall ist es wichtig, zwischen IQueryable<T> Und IEnumerable<T> Zu unterscheiden. Kurz gesagt, IQueryable<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 der Implementierer die Notwendigkeit der Anweisung nicht vorausgesehen hat.

Im Gegensatz dazu wird IEnumerable<T> Für die konkreten Objekte ausgeführt und wird daher nicht transformiert. Daher ist es durchaus üblich, dass Konstrukte, die mit IEnumerable<T> Verwendet werden können, nicht mit IQueryable<T> Verwendet werden können und dass IQueryables<T>, Die von verschiedenen LINQ-Anbietern unterstützt werden, nicht denselben Satz unterstützen von Funktionen.

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

Der obige Code könnte also auch so 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.

18
AxelEckenberger

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

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 also myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace()) ausführen, wird es in !(c.Name == null || x.Trim() == "") konvertiert, bevor es an irgendetwas übergeben (linq to sql/entity) und in sql konvertiert wird.

9
Sam

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

!String.IsNullOrEmpty(b.Diameter.Trim())
3
Majid
!String.IsNullOrEmpty(b.Diameter.Trim()) 

wird eine Ausnahme auslösen, wenn b.Diameter ist null.
Wenn Sie Ihre Aussage dennoch verwenden möchten, verwenden Sie besser diesen Scheck

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