web-dev-qa-db-ja.com

LINQ式のString.IsNullOrWhiteSpace

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

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)'メソッドを認識せず、このメソッドはストア式に変換できません。 "

この問題を解決し、これよりも良いコードを書くにはどうすればよいですか?

136

交換する必要があります

!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)
245
Phil

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

対照的に、IEnumerable<T>は具象オブジェクトに対して実行されるため、変換されません。そのため、IEnumerable<T>で使用可能な構造体はIQueryable<T>で使用できず、異なるLINQプロバイダーによってサポートされているIQueryables<T>は同じ関数セットをサポートしていません。

ただし、クエリを変更する回避策がいくつかあります( Phil's answer など)。また、より一般的なアプローチとして、クエリの仕様を続行する前に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
    );

注:このコードは、 Phil's answer よりもパフォーマンスに大きな影響を与えます。しかし、それは原理を示しています。

18
AxelEckenberger

式ビジターを使用して、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に変換されます。

9
Sam

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

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

b.Diameternullの場合、例外をスローします。
まだステートメントを使用する場合は、このチェックを使用することをお勧めします

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