LINQ表达式中的String.IsNullOrWhiteSpace

Hos*_*nia 142 c# linq null lambda linq-to-entities

我有以下代码:

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);
Run Code Online (Sandbox Code Playgroud)

当我尝试运行代码时出现此错误:

LINQ to Entities无法识别方法'Boolean IsNullOrWhiteSpace(System.String)'方法,并且此方法无法转换为商店表达式."

如何解决这个问题并编写代码比这更好?

Phi*_*hil 251

你需要更换

!string.IsNullOrWhiteSpace(b.Diameter)
Run Code Online (Sandbox Code Playgroud)

!(b.Diameter == null || b.Diameter.Trim() == string.Empty)
Run Code Online (Sandbox Code Playgroud)

对于Linq to Entities,这被转换为:

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

而对于Linq到SQL差不多但不完全相同

DECLARE @p0 NVarChar(1000) = ''
...
WHERE NOT (LTRIM(RTRIM([t0].[TypeName])) = @p0)
Run Code Online (Sandbox Code Playgroud)

  • 它可能会编译,但它不会被Linq转换为SQL到实体.*方法'Boolean IsNullOrWhiteSpace(System.String)'没有支持的SQL转换.*同样适用于IsNullOrEmpty. (36认同)
  • 为什么?这段代码编译:`List <string> my = new List <string>(); var i = from my in my!string.IsNullOrWhiteSpace(m)select m;` (3认同)
  • 提醒一句:在 ""(又名空字符串)上使用 'string.Empty' 是最重要的。前者有效,后者无效(至少就 Oracle 的 EF 驱动程序而言)。Aka 如果你使用: b.Diameter.Trim() == "" &lt;-- 这不会按预期工作(我知道疯了......) (3认同)

Axe*_*ger 18

在这种情况下,区分IQueryable<T>和之间很重要IEnumerable<T>.简而言之IQueryable<T>,由LINQ提供程序处理以提供优化的查询.在此转换期间,并非所有C#语句都受支持,因为它们无法将它们转换为后端特定查询(例如SQL),或者因为实现者没有预见到需要该语句.

相反IEnumerable<T>,针对具体对象执行,因此不会被转换.因此,很常见的是,可以使用的构造IEnumerable<T>不能与之一起使用,IQueryable<T>并且IQueryables<T>由不同的LINQ提供程序支持的构造也不支持相同的功能集.

但是,有一些解决方法(如Phil的答案),它们会修改查询.此外,作为更通用的方法,可以IEnumerable<T>在继续查询规范之前回退到a .但是,这可能会影响性能 - 尤其是在限制时使用它(例如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
    );
Run Code Online (Sandbox Code Playgroud)

注意:代码将比Phil的答案具有更高的性能影响.但是,它显示了原则.


Sam*_*Sam 9

使用表达式visitor来检测对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"));
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,如果你运行myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace())它将被转换为!(c.Name == null || x.Trim() == "")传递给任何东西(linq到sql/entities)并转换为sql.