|| 使用C#的Linq中的(或)运算符

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

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

例如

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)将等于true而String.IsNullOrEmpty(fromname)的第二部分 不会跑。

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

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

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

有帮助吗?

阅读本文档 ,了解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也都是非null。

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

 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是一个变量,因此转换机制会关闭并执行对它的要求(即使它为null,也会触发exception,从而生成fromname的小写表示)。

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

也许更好的是:

 IEnumerable 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; } 

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

好的。 我找到解决方案。

我将违规行更改为:

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

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

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

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

如果您使用的是LINQ to SQL, 则不能指望SQL Server中存在相同的C#短路行为。 请参阅SQL Server中有关短路WHERE子句(或缺少子句)的问题。

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

  1. 方法String.IsNullOrEmpty(String)没有支持的SQL转换,因此您无法在LINQ to SQL中使用它。
  2. 你不会得到NullReferenceException。 这是一个托管exception,它只发生在客户端,而不是在SQL Server中。

你确定这不是通过LINQ to Objects到达某个地方吗? 您是在源上调用ToList()或ToArray()还是在运行此查询之前将其作为IEnumerable 引用?


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

让你的解决方案看起来不那么hacky的一种方法是在查询之外解析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。 如果为null,则将在没有WHERE子句的情况下进行转换。 如果它不为null,它将使用简单的“ WHERE @theValue = theValue ”进行转换,而不使用T-SQL中的空值检查。

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

你确定它的’fromname’是null而不是’msg.FromName’是null吗?

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

你是正确的,第二个条件不应该被评估,因为你正在使用短路比较器(参见关于C#短路评估的最佳实践是什么? ),但是我怀疑Linq可能会尝试优化你的在执行之前进行查询,这样做可能会改变执行顺序。

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