LINQexpression式中的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不识别方法'布尔IsNullOrWhiteSpace(System.String)'方法,并且此方法不能被转换成存储expression式。
我怎样才能解决这个问题,编写比这更好的代码呢?
你需要更换
!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>
ist由LINQ提供程序处理以提供优化的查询。 在这个转换过程中,并不是所有的C#语句都被支持,因为它不可能将它们翻译成后端特定的查询(例如SQL),或者因为实现者不需要这个语句。
在契约中IEnumerable<T>
是针对具体对象执行的,因此不会被转换。 因此,可以使用IEnumerable<T>
构造不能与IQueryable<T>
一起使用,而且由不同的LINQ提供者支持的IQueryables<T>
不支持相同的一组函数。
但是,有一些解决方法(如菲尔的答案 ),它修改查询。 另外,作为一个更一般的方法,可以在继续查询规范之前退回到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的回答有更高的性能影响。 但是,它显示了原则。
使用expression式访问器来检测对string.IsNullOrWhiteSpace的引用,并将它们分解为更简单的expression式(x == null || x.Trim() == string.Empty)
。
所以下面是一个扩展访问者和扩展方法来利用它。 它不需要使用特殊的configuration,只需调用WhereEx而不是Where。
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到SQL /实体)并转换为SQL。
你也可以用它来检查空格:
!(String.IsNullOrEmpty(b.Diameter.Trim());