|| (或)Linq中使用C#的操作员

时间:2009-04-21 12:10:36

标签: c# .net linq linq-to-sql null

我正在使用linq过滤选择的MessageItems。我编写的方法接受一堆可能为null的参数。如果它们为null,则应忽略该文件的条件。如果它不为null,则使用它来过滤结果。

我的理解是在做一个||时operation是C#,如果第一个表达式为true,则不应计算第二个表达式。

e.g。

if(ExpressionOne() || ExpressionTwo())
{
     // only ExpressionOne was evaluated because it was true
}

现在,在linq,我正在尝试这个:

var messages = (from msg in dc.MessageItems
where  String.IsNullOrEmpty(fromname) || (!String.IsNullOrEmpty(fromname) && msg.FromName.ToLower().Contains(fromname.ToLower()))
select msg);

我原本以为这会是合理的,因为String.IsNullOrEmpty(fromname)等于真,而第二部分是||不会跑。

然而它确实运行了,第二部分

msg.FromName.ToLower().Contains(fromname.ToLower()))

抛出空引用异常(因为fromname为空)!! - 我得到一个经典的“对象引用未设置为对象的实例”异常。

任何帮助?

6 个答案:

答案 0 :(得分:14)

阅读this documentation,解释了linq和c#如何断开连接。

由于Linq表达式应该简化为普通方法以外的其他方法,因此如果以后在某些非Linq to Objects上下文中使用它,则可能会发现此代码中断。

那说

String.IsNullOrEmpty(fromname) || 
(   !String.IsNullOrEmpty(fromname) && 
    msg.FromName.ToLower().Contains(fromname.ToLower())
)

因为它真的应该

而形成得很糟糕
String.IsNullOrEmpty(fromname) || 
msg.FromName.ToLower().Contains(fromname.ToLower())

这使得你很好并且很清楚你依赖于msg和msg.FromName也都是非空的。

为了让您的生活更轻松,您可以添加以下字符串扩展方法

public static class ExtensionMethods
{
    public static bool Contains(
        this string self, string value, StringComparison comparison)
    {
        return self.IndexOf(value, comparison) >= 0;
    }

    public static bool ContainsOrNull(
        this string self, string value, StringComparison comparison)
    {
        if (value == null)
            return false;
        return self.IndexOf(value, comparison) >= 0;
    }
}

然后使用:

var messages = (from msg in dc.MessageItems
where  msg.FromName.ContainsOrNull(
    fromname, StringComparison.InvariantCultureIgnoreCase)
select msg);

然而,这不是问题。问题是系统的Linq to SQL方面正在尝试使用fromname值来构造发送到服务器的查询

由于fromname是一个变量,因此转换机制会关闭并执行对它的要求(生成fromname的小写表示,即使它为null,也会触发异常)。

在这种情况下,您可以执行您已经发现的操作:保持查询不变,但请确保始终创建一个非null的fromname值,即使它为null,也会显示所需的行为。

也许更好的是:

IEnumerable<MessageItem> results;
if (string.IsNullOrEmpty(fromname))
{ 
    results = from msg in dc.MessageItems 
    select msg;    
}
else
{
    results = from msg in dc.MessageItems 
    where msg.FromName.ToLower().Contains(fromname) 
    select msg;    
}

这不是很好,它的查询包含其他约束,因此调用了更多重复但是对于简单查询实际上应该导致更易读/可维护的代码。如果您依赖匿名类型,这会很痛苦,但希望这对您来说不是问题。

答案 1 :(得分:5)

好。我找到了 A 解决方案。

我将违规行更改为:

where (String.IsNullOrEmpty(fromemail)  || (msg.FromEmail.ToLower().Contains((fromemail ?? String.Empty).ToLower())))

它有效,但感觉就像一个黑客。我确定第一个表达式是否为真,第二个表达式不应该被评估。

如果有人能够为我确认或否认这一点会很棒......

如果有人有更好的解决方案,请告诉我!!!

答案 2 :(得分:4)

如果您使用的是LINQ to SQL,则无法期望SQL Server中出现相同的C#短路行为。请参阅this question有关SQL Server中的短路WHERE子句(或缺少该子句)。

另外,正如我在评论中提到的,我不相信你在LINQ to SQL中得到这个异常,因为:

  1. 方法String.IsNullOrEmpty(String)没有支持的SQL转换,因此您无法在LINQ to SQL中使用它。
  2. 你不会得到NullReferenceException。这是一个托管异常,它只发生在客户端,而不是在SQL Server中。
  3. 你确定这不是通过LINQ to Objects到达某个地方吗?您是在源上调用ToList()或ToArray()还是将其引用为IEnumerable&lt; T&gt;在运行此查询之前?


    更新:在阅读完您的评论后,我再次对此进行了测试并实现了一些功能。你不使用LINQ to SQL我错了。您没有收到"String.IsNullOrEmpty(String) has no supported translation to SQL"异常,因为在本地变量而不是SQL列上调用了IsNullOrEmpty(),所以即使您使用的是 正在运行客户端LINQ to SQL(不是LINQ to Objects)。由于它在客户端运行,因此可以在该方法调用上获得NullReferenceException,因为它没有转换为SQL,在那里您无法获得NullReferenceException

    使解决方案看起来不那么苛刻的一种方法是在查询之外解析fromname的“null-ness”:

    string lowerfromname = String.IsNullOrEmpty(fromname) ? fromname : fromname.ToLower();
    
    var messages = from msg in dc.MessageItems
                   where String.IsNullOrEmpty(lowerfromname) || msg.Name.ToLower().Contains(lowerfromname)
                   select msg.Name;
    

    请注意,这并不总是转换为(使用您的评论作为示例):

    SELECT ... FROM ... WHERE @theValue IS NULL OR @theValue = theValue
    

    它的翻译将在运行时决定,具体取决于fromname是否为空。如果为null,则将在没有WHERE子句的情况下进行转换。如果它不为null,它将使用简单的“WHERE @theValue = theValue”进行转换,而不会在T-SQL中进行空检查。

    所以最后,在这种情况下,它是否会在SQL中短路的问题是无关紧要的,因为如果fromname为空,LINQ to SQL运行时将发出不同的T-SQL查询。从某种意义上说,在查询数据库之前,它在客户端是短路的。

答案 3 :(得分:3)

你确定'fromname'是null而不是'msg.FromName'是null吗?

答案 4 :(得分:0)

就像Brian说的那样,在执行ToLower()之前,我会查看msg.FromName是否为null。包含(fromname.ToLower()))

答案 5 :(得分:0)

你是正确的,第二个条件不应该被评估,因为你正在使用短路比较器(见What is the best practice concerning C# short-circuit evaluation?),但是我怀疑Linq可能会在执行之前尝试优化你的查询这样做可能会改变执行顺序。

对于我来说,将整个事物包装在括号中也会使得更清晰的陈述,因为整个'where'条件包含在父语中。