【问题标题】:String.IsNullOrWhiteSpace in LINQ ExpressionLINQ 表达式中的 String.IsNullOrWhiteSpace
【发布时间】:2012-03-25 07:20:01
【问题描述】:

我有以下代码:

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)' 方法,并且该方法不能 翻译成商店表达式。”

我怎样才能解决这个问题并写出比这更好的代码?

【问题讨论】:

    标签: c# linq lambda null linq-to-entities


    【解决方案1】:

    你需要更换

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

    【讨论】:

    • 为什么?此代码编译:List<string> my = new List<string>(); var i = from m in my where !string.IsNullOrWhiteSpace(m) select m;
    • 它可以编译,但它不会被Linq to entity翻译成SQL。 方法 'Boolean IsNullOrWhiteSpace(System.String)' 不支持对 SQL 的转换。 这同样适用于 IsNullOrEmpty。
    • Linq to SQL 也是如此
    • 请注意:使用 'string.Empty' 而不是 ""(又名空字符串)至关重要。前者有效,后者无效(至少就 Oracle 的 EF 驱动程序而言)。 Aka 如果您使用: b.Diameter.Trim() == ""
    • 似乎 Trim() 至少对于使用 MongoDB.Driver 的查询也不支持
    【解决方案2】:

    在这种情况下,区分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。但是,它显示了原理。

    【讨论】:

      【解决方案3】:

      使用表达式访问者检测对 string.IsNullOrWhiteSpace 的引用并将它们分解为更简单的表达式 (x == null || x.Trim() == string.Empty)

      所以下面是一个扩展的访问者和一个使用它的扩展方法。它不需要特殊的配置,只需调用 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=&gt; !c.Name.IsNullOrWhiteSpace()),它将被转换为 !(c.Name == null || x.Trim() == ""),然后再传递给任何内容(linq 到 sql/entities)并转换为 sql。

      【讨论】:

      • 对于这么简单的要求,比菲尔的回答要复杂得多,但对于 ExpressionVisitor 的教育目的非常有趣,谢谢
      【解决方案4】:

      您也可以使用它来检查空格:

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

      【讨论】:

      • 如果直径为空,这将引发异常。
      • @OkanKocyigit 你是对的。我已经编辑了答案。 :)
      【解决方案5】:
      !String.IsNullOrEmpty(b.Diameter.Trim()) 
      

      如果b.Diameternull,将抛出异常。
      如果您仍想使用您的语句,最好使用此检查

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

      【讨论】:

      • 欢迎来到 StackOverflow!首先,感谢您作为回答者参与 SO。请查看formatting 以创建清晰易读的答案。
      猜你喜欢
      • 1970-01-01
      • 2012-08-20
      • 1970-01-01
      • 1970-01-01
      • 2020-08-11
      • 2022-12-22
      • 2018-09-29
      • 2011-11-22
      • 1970-01-01
      相关资源
      最近更新 更多