LINQ式のString.IsNullOrWhiteSpace


151

私は次のコードを持っています:

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

コードを実行しようとすると、このエラーが発生します。

LINQ to Entitiesは 'Boolean IsNullOrWhiteSpace(System.String)'メソッドを認識せず、このメソッドはストア式に変換できません。

この問題を解決して、これよりも優れたコードを作成するにはどうすればよいですか?

回答:


263

交換する必要があります

!string.IsNullOrWhiteSpace(b.Diameter)

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

Linq to Entitiesの場合、これは次のように変換されます。

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

Linq to SQLの場合、ほとんど同じではありません

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

3
どうして?このコードはコンパイルされます:List<string> my = new List<string>(); var i = from m in my where !string.IsNullOrWhiteSpace(m) select m;
エリックJ.

37
コンパイルすることはできますが、LinqによってエンティティにSQLに変換されません。 メソッド 'Boolean IsNullOrWhiteSpace(System.String)'には、SQLへのサポートされている変換がありません。IsNullOrEmptyについても同様です。
Phil

1
Linq to SQLについても同じことが言えます
Phil

3
注意: ""(空の文字列)よりも 'string.Empty'を使用することが最も重要です。前者は後者で機能しません(少なくともOracleのEFドライバーに関する限り)。別名:次を使用する場合:b.Diameter.Trim()== "" <-これは意図したとおりに動作しません(狂ったように私は知っています...)
XDS

Trim()も少なくともMongoDB.Driverを使用するクエリではサポートされていないようです
LeandrohereñuApr

20

このケースでは、区別することが重要であるIQueryable<T>IEnumerable<T>。つまりIQueryable<T>、最適化されたクエリを配信するためにLINQプロバイダーによって処理されます。この変換中に、バックエンド固有のクエリ(SQLなど)に変換できないか、実装者がステートメントの必要性を予測していなかったため、すべてのC#ステートメントがサポートされているわけではありません。

対照的にIEnumerable<T>、具体的なオブジェクトに対して実行されるため、変換されません。だから、で使用可能である構築物は、ということは非常に一般的であるIEnumerable<T>、で使用することはできませんIQueryable<T>し、また、そのIQueryables<T>機能の同じセットをサポートしていない別のLINQプロバイダによって裏打ちされました。

ただし、クエリを変更するいくつかの回避策(フィルの回答など)があります。また、より一般的なアプローチとしてIEnumerable<T>、クエリの指定を続行する前に前に戻すことができます。ただし、これはパフォーマンスに影響を与える可能性があります-特に制限(where句など)で使用する場合。対照的に、変換を処理する場合、クエリによっては、パフォーマンスへの影響ははるかに小さく、場合によっては存在しません。

したがって、上記のコードは次のように書き換えることもできます。

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

注:このコードは、フィルの回答よりもパフォーマンスに影響します。しかし、それは原理を示しています。


10

式ビジターを使用してstring.IsNullOrWhiteSpaceへの参照を検出し、それらをより単純な式に分解します(x == null || x.Trim() == string.Empty)

以下は拡張ビジターとそれを利用するための拡張メソッドです。特別な設定を使用する必要はなく、Whereの代わりにWhereExを呼び出すだけです。

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

したがって、実行myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace())すると!(c.Name == null || x.Trim() == "")、何にでも渡される前に変換され(linq to sql / entities)、sqlに変換されます。


このような単純な要件に対するPhilの回答よりもはるかに複雑ですが、ExpressionVisitorに関する教育目的には非常に興味深いものです。ありがとう
AFract

2

これを使用して空白をチェックすることもできます:

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

6
直径がnullの場合、これは例外をスローします。
Okan Kocyigit 2018年

@OkanKocyigitあなたは正しいです。回答を編集しました。:)
マジッド

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

場合に例外をスローしますb.Diameterですnull
それでもステートメントを使用したい場合は、このチェックを使用することをお勧めします

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

2
StackOverflowへようこそ!まず、SOに回答者としてご参加いただきありがとうございます。明確で読みやすい回答を作成するために、フォーマットを確認してください。
Hille、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.