私は次のコードを持っています:
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)'メソッドを認識せず、このメソッドはストア式に変換できません。 "
この問題を解決し、これよりも良いコードを書くにはどうすればよいですか?
交換する必要があります
!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)
この場合、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 よりもパフォーマンスに大きな影響を与えます。しかし、それは原理を示しています。
式ビジターを使用して、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に変換されます。
これを使用して、空白をチェックすることもできます。
!String.IsNullOrEmpty(b.Diameter.Trim())
!String.IsNullOrEmpty(b.Diameter.Trim())
b.Diameter
がnull
の場合、例外をスローします。
まだステートメントを使用する場合は、このチェックを使用することをお勧めします
!String.IsNullOrWhiteSpace(b.Diameter), IsNullOrWhiteSpace = IsNullOrEmpty + WhiteSpace