【问题标题】:The || (or) Operator in Linq with C#|| (或)使用 C# 的 Linq 中的运算符
【发布时间】:2010-10-20 19:28:59
【问题描述】:

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

据我了解,在执行 || 时操作是 C#,如果第一个表达式为真,则不应计算第二个表达式。

例如

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 并且 || 的第二部分不会跑。

但它确实会运行,第二部分

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

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

有什么帮助吗?

【问题讨论】:

  • 了解此 LINQ to Objects 是否很重要? LINQ到SQL? LINQ 到实体?等等...你的假设 || will 快捷方式仅在 LINQ to Objects 中得到保证,因为您的代码未翻译(例如,T-SQL)。
  • 啊酷。好的卢卡斯,所以我正在使用 Linq to SQL。我想你真的知道我在这里的意思。所以你在 Linq to SQL 中说你不会指望这会起作用?我应该如何在 Linq to SQL 中解决这个问题?我只想说:如果传递的参数为null,那么无论匹配到哪个都获取记录,如果不为null,则确保返回的每条记录都与传递的参数匹配。非常感谢!
  • Ev,如果这是 linq to sql,那么短路评估只是 不是你可以依赖的东西句号
  • 好吧,这很酷@ShuggyCoUk。我非常感谢您的意见!你明白我在这里想要做什么吗?听起来我完全错过了一些东西!我知道这可以在 SQL 中工作,而且我知道它可以在 C# 中工作,所以在 Linq to SQL 中必须有某种等价物。我对吗?所以,我想说的是,在这个特定的 where 子句中,如果参数 fromname 为空,要么获取所有行。如果 fromname 不是 NULL,请确保我们匹配参数。抓住我?再次感谢您的帮助伙伴
  • 您没有使用 LINQ to SQL,因为 (1) 方法 'Boolean IsNullOrEmpty(System.String)' 不支持对 SQL 的转换,并且 (2) 您不会收到 NullReferenceException(它会只发生在客户端,不在数据库上)。

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


【解决方案1】:

阅读 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 两者都不为空。

为了让您在 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 是一个变量,因此翻译机制会关闭并执行所要求的操作(即使fromname 为空,也会生成小写表示,这会触发异常)。

在这种情况下,您可以执行您已经发现的操作:保持查询不变,但确保始终可以创建具有所需行为的非 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;    
}

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

【讨论】:

  • 干杯舒吉。我现在将浏览这些文档。我同意,这有点糟透了——不需要“!String.IsNullOrEmpty(fromname) &&”(我认为在我一直在测试的时候已经出现了),但这仍然解决了我的问题。我已将该行更新为 String.IsNullOrEmpty(fromname) || msg.FromName.ToLower().Contains(fromname.ToLower()) 正如你所建议的 - 现在可用。我现在将浏览文档。如果您有其他想法,请发布!
  • @Ev 你对 (a == null || use(a)) 的印象是合法的,这在 Linq 中是错误,很简单很抱歉。你用修改后的 where 子句尝试了我的扩展方法吗?
  • 由于不能保证像 sql 引擎这样的东西会进行那种短路评估(不诉诸诸如 case 之类的蹩脚的东西),Linq 系统不保证会发生这种情况(因此有关于它的文档)。您不必编写扩展名 - 它已经存在于答案中。
  • 嗯,好的。我假设您正在将数据拉回,然后进行过滤。这是行不通的。我会相应地编辑
  • 无论你的解决方案是什么,基本上都必须为 Contains 提供一个非 null 值,或者一起避免它(就像我的那样)抱歉
【解决方案2】:

好的。我找到了一个解决方案。

我将违规行改为:

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

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

如果有人可以为我确认或否认这一点,那就太好了...

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

【讨论】:

    【解决方案3】:

    如果您使用的是 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 中。

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


    更新:在阅读了您的 cmets 之后,我再次对此进行了测试并意识到了一些事情。我错了你不使用 LINQ to SQL。您没有收到 "String.IsNullOrEmpty(String) has no supported translation to SQL" 异常,因为 IsNullOrEmpty() 是在局部变量上调用的,而不是 SQL 列,因此它在客户端运行,即使您使用的是 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;
    

    请注意,这并不总是被翻译成类似(以您的 cmets 为例):

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

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

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

    【讨论】:

    • 我正在调用 ToList(),这是 linq 运行的时候,但不是之前。如果我不能指望相同的短路行为,那很好。我现在对此很满意。我确实找到了解决方案,但我仍然对此并不满意。我的解决方案是更改 String.IsNullOrEmpty(fromname) || msg.FromName.ToLower().Contains(fromname.ToLower()) 到 where ((fromname == null) || (msg.FromName.IndexOf(fromname) >= 0)) 所以我不认为 fromname 不是空值。我不认为这是最好的方法。我想要的东西会变成相当于
    • SELECT * FROM tblSomeTable WHERE @theValue 为 NULL 或 @theValue = theValue
    • 如果您有任何想法,那就太好了!
    • 谢谢,您的 cmets 让我意识到我犯了一个错误。我已经更新了我的答案。
    【解决方案4】:

    您确定是 'fromname' 为空,而不是 'msg.FromName' 为空吗?

    【讨论】:

    • 嘿 - 感谢您的快速回复!是的,fromname 为空。我通过删除 ToLower() 进行了测试,但我没有问题。
    • 所以只是为了澄清-它是空的fromname-而不是msg.FromName。还有其他想法吗?
    • @ShuggyCoUk 说了什么。 dc.MessageItems 的类型是什么?它有一个花哨的 LINQ 提供程序吗?
    • 嗨,布莱恩。是我自己的课。我正在使用 Linq to SQL,所以它是一个对象,基本上代表一个表。成员是诸如 msg.FromName、msg.Email、msg.Location、msg.message Text 之类的东西——虽然没有疯狂的提供者。所以 dc.MessageItems 基本上是 MessageItem 的集合,我只需要知道为什么要评估 or 操作的第二部分,即使第一个表达式是“True”! :) 感谢您的帮助!
    【解决方案5】:

    就像布赖恩说的,我会在执行 ToLower().Contains(fromname.ToLower())) 之前查看 msg.FromName 是否为空

    【讨论】:

    • 干杯北欧人。就像我说的,我确信 msg.FromName 在这种情况下不为空。你们如何看待评价的第二部分?我不应该是对的吗?
    【解决方案6】:

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

    对我来说,将整个内容括在括号中也可以更清楚地说明,因为整个“where”条件都包含在括号中。

    【讨论】:

    • @Lazarus。感谢您的评论。 Linq 正在优化这些短路比较器并失去我所依赖的功能听起来真的很可行。任何想法如何在 Linq 中强制它?感谢您的评论!
    • 应该提到我已经尝试了很多不同的括号选项!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-19
    相关资源
    最近更新 更多