LINQ表达式中的String.IsNullOrWhiteSpace


151

我有以下代码:

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

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

Answers:


263

您需要更换

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

3
为什么?这段代码编译:List<string> my = new List<string>(); var i = from m in my where !string.IsNullOrWhiteSpace(m) select m;
Eric J.

37
它可以编译,但不会被Linq转换为SQL到实体。 方法'Boolean IsNullOrWhiteSpace(System.String)'不支持SQL转换。IsNullOrEmpty也是如此。
菲尔(Phil)

1
同样是真实的LINQ到SQL
菲尔-

3
请注意:在“”(也称为空字符串)上使用'string.Empty'是至关重要的。前者不起作用(至少就Oracle的EF驱动程序而言)。如果您使用Aka,也可以使用:b.Diameter.Trim()==“” <-不能按预期工作(疯狂,我知道...)
XDS

至少使用MongoDB.Driver的查询似乎还不支持Trim()
Leandrohereñu

20

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

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

但是,有一些变通办法(例如Phil的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的答案对性能产生更高的影响。但是,它显示了原理。


10

使用表达式访问者来检测对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=> !c.Name.IsNullOrWhiteSpace())它,它将!(c.Name == null || x.Trim() == "")在传递给任何内容(对SQL /实体的Linq)并转换为sql之前将其转换为。


对于这样一个简单的要求,它比Phil的答案复杂得多,但对于ExpressionVisitor的教育目的却非常有趣,谢谢
AFract

2

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

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

6
如果直径为null,则将引发异常。
Okan Kocyigit,

@OkanKocyigit你是对的。我已经编辑了答案。:)
Majid

0
!String.IsNullOrEmpty(b.Diameter.Trim()) 

如果b.Diameter为,将抛出异常null
如果您仍要使用语句,最好使用此检查

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

2
欢迎来到StackOverflow!首先,感谢您以答复者身份参与SO。请查看格式以创建清晰易读的答案。
希勒,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.